dkimverify 0.0.1
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 +7 -0
- data/.gitignore +4 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +20 -0
- data/README.md +28 -0
- data/dkim-query/.gitignore +51 -0
- data/dkim-query/.rspec +1 -0
- data/dkim-query/.travis.yml +16 -0
- data/dkim-query/.yardopts +1 -0
- data/dkim-query/ChangeLog.md +25 -0
- data/dkim-query/Gemfile +19 -0
- data/dkim-query/LICENSE.txt +20 -0
- data/dkim-query/README.md +105 -0
- data/dkim-query/Rakefile +24 -0
- data/dkim-query/bin/dkim-query +34 -0
- data/dkim-query/dkim-query.gemspec +26 -0
- data/dkim-query/lib/dkim/query/domain.rb +141 -0
- data/dkim-query/lib/dkim/query/exceptions.rb +8 -0
- data/dkim-query/lib/dkim/query/key.rb +162 -0
- data/dkim-query/lib/dkim/query/malformed_key.rb +36 -0
- data/dkim-query/lib/dkim/query/parser.rb +175 -0
- data/dkim-query/lib/dkim/query/query.rb +74 -0
- data/dkim-query/lib/dkim/query/version.rb +6 -0
- data/dkim-query/lib/dkim/query.rb +4 -0
- data/dkim-query/spec/domain_spec.rb +96 -0
- data/dkim-query/spec/key_spec.rb +117 -0
- data/dkim-query/spec/malformed_key.rb +15 -0
- data/dkim-query/spec/parser_spec.rb +300 -0
- data/dkim-query/spec/query_spec.rb +68 -0
- data/dkim-query/spec/spec_helper.rb +13 -0
- data/dkim-query/tasks/alexa.rb +43 -0
- data/dkimverify.gemspec +16 -0
- data/dkimverify.rb +256 -0
- metadata +104 -0
@@ -0,0 +1,300 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dkim/query/parser'
|
3
|
+
|
4
|
+
describe Parser do
|
5
|
+
describe ".parse" do
|
6
|
+
subject { described_class }
|
7
|
+
|
8
|
+
let(:dkim) do
|
9
|
+
%{k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrEee0Ri4Juz+QfiWYui/E9UGSXau/2P8LjnTD8V4Unn+2FAZVGE3kL23bzeoULYv4PeleB3gfmJiDJOKU3Ns5L4KJAUUHjFwDebt0NP+sBK0VKeTATL2Yr/S3bT/xhy+1xtj4RkdV7fVxTn56Lb4udUnwuxK4V5b5PdOKj/+XcwIDAQAB; n=A 1024 bit key;}
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should parse a DKIM record into a Hash" do
|
13
|
+
expect(subject.parse(dkim)).to be == {
|
14
|
+
k: :rsa,
|
15
|
+
p: %{MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrEee0Ri4Juz+QfiWYui/E9UGSXau/2P8LjnTD8V4Unn+2FAZVGE3kL23bzeoULYv4PeleB3gfmJiDJOKU3Ns5L4KJAUUHjFwDebt0NP+sBK0VKeTATL2Yr/S3bT/xhy+1xtj4RkdV7fVxTn56Lb4udUnwuxK4V5b5PdOKj/+XcwIDAQAB},
|
16
|
+
n: "A 1024 bit key;"
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "tags" do
|
22
|
+
describe "v" do
|
23
|
+
subject { super().key_v_tag }
|
24
|
+
|
25
|
+
it "should parse v=DKIM1" do
|
26
|
+
expect(subject.parse('v=DKIM1')).to be == {
|
27
|
+
name: 'v',
|
28
|
+
value: {symbol: 'DKIM1'}
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "g" do
|
34
|
+
subject { super().key_g_tag }
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "h" do
|
38
|
+
subject { super().key_h_tag }
|
39
|
+
|
40
|
+
it "should parse h=sha1" do
|
41
|
+
expect(subject.parse('h=sha1')).to be == {
|
42
|
+
name: 'h',
|
43
|
+
value: {symbol: 'sha1'}
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should parse h=sha256" do
|
48
|
+
expect(subject.parse('h=sha256')).to be == {
|
49
|
+
name: 'h',
|
50
|
+
value: {symbol: 'sha256'}
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should parse h=sha1:sha256" do
|
55
|
+
expect(subject.parse('h=sha1:sha256')).to be == {
|
56
|
+
name: 'h',
|
57
|
+
value: [
|
58
|
+
{symbol: 'sha1'},
|
59
|
+
{symbol: 'sha256'}
|
60
|
+
]
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should parse h=abc-123-xyz" do
|
65
|
+
expect(subject.parse('h=abc-123-xyz')).to be == {
|
66
|
+
name: 'h',
|
67
|
+
value: {string: 'abc-123-xyz'}
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "k" do
|
73
|
+
subject { super().key_k_tag }
|
74
|
+
|
75
|
+
it "should parse k=rsa" do
|
76
|
+
expect(subject.parse('k=rsa')).to be == {
|
77
|
+
name: 'k',
|
78
|
+
value: {symbol: 'rsa'}
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should parse k=abc-123-xyz" do
|
83
|
+
expect(subject.parse('k=abc-123-xyz')).to be == {
|
84
|
+
name: 'k',
|
85
|
+
value: {string: 'abc-123-xyz'}
|
86
|
+
}
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "n" do
|
91
|
+
subject { super().key_n_tag }
|
92
|
+
|
93
|
+
let(:notes) { %{A 1024 bit key} }
|
94
|
+
|
95
|
+
it "should parse n=..." do
|
96
|
+
expect(subject.parse("n=#{notes}")).to be == {
|
97
|
+
name: 'n',
|
98
|
+
value: {string: notes}
|
99
|
+
}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "p" do
|
104
|
+
subject { super().key_p_tag }
|
105
|
+
|
106
|
+
let(:base64) { %{MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrEee0Ri4Juz+QfiWYui/E9UGSXau/2P8LjnTD8V4Unn+2FAZVGE3kL23bzeoULYv4PeleB3gfmJiDJOKU3Ns5L4KJAUUHjFwDebt0NP+sBK0VKeTATL2Yr/S3bT/xhy+1xtj4RkdV7fVxTn56Lb4udUnwuxK4V5b5PdOKj/+XcwIDAQAB} }
|
107
|
+
|
108
|
+
it "should parse p=..." do
|
109
|
+
expect(subject.parse("p=#{base64}")).to be == {
|
110
|
+
name: 'p',
|
111
|
+
value: {asn1: base64}
|
112
|
+
}
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "s" do
|
117
|
+
subject { super().key_s_tag }
|
118
|
+
|
119
|
+
it "should parse s=email" do
|
120
|
+
expect(subject.parse('s=email')).to be == {
|
121
|
+
name: 's',
|
122
|
+
value: {symbol: 'email'}
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should parse s=*" do
|
127
|
+
expect(subject.parse('s=*')).to be == {
|
128
|
+
name: 's',
|
129
|
+
value: {symbol: '*'}
|
130
|
+
}
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should parse s=email:*" do
|
134
|
+
expect(subject.parse('s=email:*')).to be == {
|
135
|
+
name: 's',
|
136
|
+
value: [{symbol: 'email'}, {symbol: '*'}]
|
137
|
+
}
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should parse s=abc-123-xyz" do
|
141
|
+
expect(subject.parse('s=abc-123-xyz')).to be == {
|
142
|
+
name: 's',
|
143
|
+
value: {string: 'abc-123-xyz'}
|
144
|
+
}
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "t" do
|
149
|
+
subject { super().key_t_tag }
|
150
|
+
|
151
|
+
it "should parse t=y" do
|
152
|
+
expect(subject.parse('t=y')).to be == {
|
153
|
+
name: 't',
|
154
|
+
value: {symbol: 'y'}
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should parse t=s" do
|
159
|
+
expect(subject.parse('t=s')).to be == {
|
160
|
+
name: 't',
|
161
|
+
value: {symbol: 's'}
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should parse t=abc-123-xyz" do
|
166
|
+
expect(subject.parse('t=abc-123-xyz')).to be == {
|
167
|
+
name: 't',
|
168
|
+
value: {string: 'abc-123-xyz'}
|
169
|
+
}
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
describe "rules" do
|
175
|
+
describe "dkim_quoted_printable" do
|
176
|
+
end
|
177
|
+
|
178
|
+
describe "dkim_safe_char" do
|
179
|
+
end
|
180
|
+
|
181
|
+
describe "hyphenated_word" do
|
182
|
+
subject { super().hyphenated_word }
|
183
|
+
|
184
|
+
it "should parse abc" do
|
185
|
+
expect(subject.parse('abc')).to be == 'abc'
|
186
|
+
end
|
187
|
+
|
188
|
+
it "should parse abc123" do
|
189
|
+
expect(subject.parse('abc123')).to be == 'abc123'
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should parse a-b-c" do
|
193
|
+
expect(subject.parse('a-b-c')).to be == 'a-b-c'
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should parse a---b---c" do
|
197
|
+
expect(subject.parse('a---b---c')).to be == 'a---b---c'
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should not parse -a" do
|
201
|
+
expect { subject.parse('-a') }.to raise_error(Parslet::ParseFailed)
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should not parse a-" do
|
205
|
+
expect { subject.parse('a-') }.to raise_error(Parslet::ParseFailed)
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should not parse -" do
|
209
|
+
expect { subject.parse('-') }.to raise_error(Parslet::ParseFailed)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
describe "base64string" do
|
214
|
+
end
|
215
|
+
|
216
|
+
describe "qp_section" do
|
217
|
+
subject { super().qp_section }
|
218
|
+
|
219
|
+
it "should parse \"A\"" do
|
220
|
+
expect(subject.parse('A')).to be == 'A'
|
221
|
+
end
|
222
|
+
|
223
|
+
it "should parse \"AAA\"" do
|
224
|
+
expect(subject.parse('AAA')).to be == 'AAA'
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should parse \" A\"" do
|
228
|
+
expect(subject.parse(' A')).to be == ' A'
|
229
|
+
end
|
230
|
+
|
231
|
+
it "should parse \"A B\"" do
|
232
|
+
expect(subject.parse('A B')).to be == 'A B'
|
233
|
+
end
|
234
|
+
|
235
|
+
it "should not parse \" \"" do
|
236
|
+
expect {
|
237
|
+
subject.parse(' ')
|
238
|
+
}.to raise_error(Parslet::ParseFailed)
|
239
|
+
end
|
240
|
+
|
241
|
+
it "should not parse \"A \"" do
|
242
|
+
expect {
|
243
|
+
subject.parse('A ')
|
244
|
+
}.to raise_error(Parslet::ParseFailed)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
describe "hex_octet" do
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
describe Parser::Transform do
|
253
|
+
context "when given {symbol: ...}" do
|
254
|
+
let(:string) { 'foo' }
|
255
|
+
|
256
|
+
it "should convert the string into a Symbol" do
|
257
|
+
expect(subject.apply(symbol: string)).to be == string.to_sym
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
context "when given {tag: {name: ..., value: ...}}" do
|
262
|
+
let(:name) { 'foo' }
|
263
|
+
let(:value) { 'bar' }
|
264
|
+
|
265
|
+
it "should convert the string into Hash" do
|
266
|
+
expect(subject.apply(
|
267
|
+
{tag: {name: name, value: value}}
|
268
|
+
)).to be == {name.to_sym => value}
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
context "when given {tag: {name: ..., value: [...]}}" do
|
273
|
+
let(:name) { 'foo' }
|
274
|
+
let(:value) { ['a', 'b'] }
|
275
|
+
|
276
|
+
it "should convert the string into Hash" do
|
277
|
+
expect(subject.apply(
|
278
|
+
{tag: {name: name, value: value}}
|
279
|
+
)).to be == {name.to_sym => value}
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
context "when {tag_list: {...}}" do
|
284
|
+
let(:hash) { {foo: 'bar'} }
|
285
|
+
|
286
|
+
it "should return the single Hash" do
|
287
|
+
expect(subject.apply({tag_list: hash})).to be == hash
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
context "when {tag_list: [{...}, ...]}" do
|
292
|
+
let(:hashes) { [{foo: 'bar'}, {baz: 'quix'}] }
|
293
|
+
let(:hash) { {foo: 'bar', baz: 'quix'} }
|
294
|
+
|
295
|
+
it "should merge the Hashes together" do
|
296
|
+
expect(subject.apply({tag_list: hashes})).to be == hash
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dkim/query/query'
|
3
|
+
|
4
|
+
describe DKIM::Query do
|
5
|
+
subject { described_class }
|
6
|
+
|
7
|
+
let(:domain) { 'yahoo.com' }
|
8
|
+
|
9
|
+
describe ".host_without_tld" do
|
10
|
+
let(:tld) { ".com" }
|
11
|
+
|
12
|
+
it "should strip the last component of the domain" do
|
13
|
+
expect(subject.host_without_tld(domain)).to be == domain.chomp(tld)
|
14
|
+
end
|
15
|
+
|
16
|
+
context "when given a host with no tld" do
|
17
|
+
let(:domain) { 'test' }
|
18
|
+
|
19
|
+
it "should return the full host" do
|
20
|
+
expect(subject.host_without_tld(domain)).to be == domain
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe ".selectors_for" do
|
26
|
+
subject { described_class.selectors_for(domain) }
|
27
|
+
|
28
|
+
described_class::SELECTORS.each do |selector|
|
29
|
+
it "should include '#{selector}'" do
|
30
|
+
expect(subject).to include(selector)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should include the domain without the TLD" do
|
35
|
+
expect(subject).to include('yahoo')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe ".query" do
|
40
|
+
it "should return all found DKIM keys" do
|
41
|
+
expect(subject.query(domain)).to be == {
|
42
|
+
's1024' => 'k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrEee0Ri4Juz+QfiWYui/E9UGSXau/2P8LjnTD8V4Unn+2FAZVGE3kL23bzeoULYv4PeleB3gfmJiDJOKU3Ns5L4KJAUUHjFwDebt0NP+sBK0VKeTATL2Yr/S3bT/xhy+1xtj4RkdV7fVxTn56Lb4udUnwuxK4V5b5PdOKj/+XcwIDAQAB; n=A 1024 bit key;'
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
context "with custom selectors" do
|
47
|
+
let(:selectors) { ['google', 's1024'] }
|
48
|
+
|
49
|
+
it "should query those selectors only" do
|
50
|
+
expect(subject.query(domain, selectors: selectors)).to have_key('s1024')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "with no selectors" do
|
55
|
+
it "should not find any keys" do
|
56
|
+
expect(subject.query(domain, selectors: [])).to be_empty
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when given an invalid domain" do
|
61
|
+
let(:domain) { 'foo.bar.com' }
|
62
|
+
|
63
|
+
it "should return {}" do
|
64
|
+
expect(subject.query(domain)).to be == {}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
##
|
2
|
+
# Adapted from gist: https://gist.github.com/zerothabhishek/3015666
|
3
|
+
# Orginal author: zerothabhishek
|
4
|
+
##
|
5
|
+
|
6
|
+
require 'nokogiri'
|
7
|
+
require 'open-uri'
|
8
|
+
require 'resolv'
|
9
|
+
require 'csv'
|
10
|
+
|
11
|
+
namespace :alexa do
|
12
|
+
desc 'Scrapes the Alexa Top 500 and updates spec/data/alexa.csv'
|
13
|
+
task :scrape do
|
14
|
+
resolver = Resolv::DNS.new
|
15
|
+
|
16
|
+
CSV.open("spec/data/alexa.csv","w") do |csv|
|
17
|
+
(0..19).each do |i|
|
18
|
+
url = "http://www.alexa.com/topsites/global;#{i} "
|
19
|
+
doc = Nokogiri::HTML(open(url))
|
20
|
+
|
21
|
+
doc.css(".site-listing").each do |li|
|
22
|
+
begin
|
23
|
+
site_name = li.css(".desc-container .desc-paragraph a")[0].content
|
24
|
+
site_rank = li.css(".count")[0].content
|
25
|
+
|
26
|
+
puts "Resolving #{site_name} ..."
|
27
|
+
dmarc = begin
|
28
|
+
resolver.getresource(
|
29
|
+
"_dmarc.#{site_name.downcase}",
|
30
|
+
Resolv::DNS::Resource::IN::TXT
|
31
|
+
).strings.join
|
32
|
+
rescue Resolv::ResolvError
|
33
|
+
end
|
34
|
+
|
35
|
+
csv << [site_rank, site_name, dmarc]
|
36
|
+
rescue => exception
|
37
|
+
warn exception.message
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/dkimverify.gemspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = "dkimverify"
|
5
|
+
gem.version = '0.0.1'
|
6
|
+
gem.authors = ["Jeremy B. Merrill"]
|
7
|
+
gem.license = "MIT"
|
8
|
+
gem.email = ["jeremybmerrill@gmail.com"]
|
9
|
+
gem.description = %q{ A pure-Ruby library for validating/verifying DKIM signatures. }
|
10
|
+
gem.summary = %q{ A pure-Ruby library for validating/verifying DKIM signatures. }
|
11
|
+
gem.homepage = "https://github.com/jeremybmerrill/dkimverify"
|
12
|
+
gem.files = `git ls-files`.split($/)
|
13
|
+
gem.require_paths = ["dkim-query"]
|
14
|
+
gem.add_dependency "mail", "2.6.4"
|
15
|
+
gem.add_dependency "parslet", "~> 1.6"
|
16
|
+
end
|