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,162 @@
|
|
1
|
+
require_relative 'exceptions'
|
2
|
+
require_relative 'parser'
|
3
|
+
require_relative 'malformed_key'
|
4
|
+
|
5
|
+
module DKIM
|
6
|
+
module Query
|
7
|
+
#
|
8
|
+
# Represents an individual DKIM signing key.
|
9
|
+
#
|
10
|
+
class Key
|
11
|
+
|
12
|
+
# DKIM version.
|
13
|
+
#
|
14
|
+
# @return [:DKIM1]
|
15
|
+
attr_reader :v
|
16
|
+
alias version v
|
17
|
+
|
18
|
+
# `g=` tag.
|
19
|
+
#
|
20
|
+
# @return [String, nil]
|
21
|
+
attr_reader :g
|
22
|
+
alias granularity g
|
23
|
+
|
24
|
+
# `h=` tag.
|
25
|
+
#
|
26
|
+
# @return [:sha1, :sha256, Array<:sha1, :sha256, String>, nil]
|
27
|
+
attr_reader :h
|
28
|
+
alias hash h
|
29
|
+
|
30
|
+
# `k=` tag.
|
31
|
+
#
|
32
|
+
# @return [:rsa, String]
|
33
|
+
attr_reader :k
|
34
|
+
alias key k
|
35
|
+
|
36
|
+
# `n=` tag.
|
37
|
+
#
|
38
|
+
# @return [String, nil]
|
39
|
+
attr_reader :n
|
40
|
+
alias notes n
|
41
|
+
|
42
|
+
# `p=` tag.
|
43
|
+
#
|
44
|
+
# @return [String, nil]
|
45
|
+
attr_reader :p
|
46
|
+
alias public_key p
|
47
|
+
|
48
|
+
# `s=` tag.
|
49
|
+
#
|
50
|
+
# @return [:email, :*, String, Array<:email, :*, String>, nil]
|
51
|
+
attr_reader :s
|
52
|
+
alias service_type s
|
53
|
+
|
54
|
+
# `t=` tag.
|
55
|
+
#
|
56
|
+
# @return [:y, :s, String, nil]
|
57
|
+
attr_reader :t
|
58
|
+
alias flags t
|
59
|
+
|
60
|
+
#
|
61
|
+
# Initialize the key.
|
62
|
+
#
|
63
|
+
# @param [Hash{Symbol => Symbol,String}] tags
|
64
|
+
# Tags for the key.
|
65
|
+
#
|
66
|
+
# @option tags [Symbol] :v
|
67
|
+
#
|
68
|
+
# @option tags [Symbol] :g
|
69
|
+
#
|
70
|
+
# @option tags [Symbol] :h
|
71
|
+
#
|
72
|
+
# @option tags [Symbol] :k
|
73
|
+
#
|
74
|
+
# @option tags [Symbol] :n
|
75
|
+
#
|
76
|
+
# @option tags [Symbol] :p
|
77
|
+
#
|
78
|
+
# @option tags [Symbol] :s
|
79
|
+
#
|
80
|
+
# @option tags [Symbol] :t
|
81
|
+
#
|
82
|
+
def initialize(tags={})
|
83
|
+
@v, @g, @h, @k, @n, @p, @s, @t = tags.values_at(:v,:g,:h,:k,:n,:p,:s,:t)
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# Parses a DKIM Key record.
|
88
|
+
#
|
89
|
+
# @param [String] record
|
90
|
+
# The DKIM key record.
|
91
|
+
#
|
92
|
+
# @return [Key]
|
93
|
+
# The new key.
|
94
|
+
#
|
95
|
+
# @raise [InvalidKey]
|
96
|
+
# Could not parse the DKIM Key record.
|
97
|
+
#
|
98
|
+
def self.parse!(record)
|
99
|
+
new(Parser.parse(record))
|
100
|
+
rescue Parslet::ParseFailed => error
|
101
|
+
raise(InvalidKey.new(error.message,error.cause))
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Parses a DKIM Key record.
|
106
|
+
#
|
107
|
+
# @param [String] record
|
108
|
+
# The DKIM key record.
|
109
|
+
#
|
110
|
+
# @return [Key, MalformedKey]
|
111
|
+
# The parsed key. If the key could not be parsed, a {MalformedKey}
|
112
|
+
# will be returned.
|
113
|
+
#
|
114
|
+
def self.parse(record)
|
115
|
+
begin
|
116
|
+
parse!(record)
|
117
|
+
rescue Parslet::ParseFailed => error
|
118
|
+
MalformedKey.new(record,error.cause)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
#
|
123
|
+
# Converts the key to a Hash.
|
124
|
+
#
|
125
|
+
# @return [Hash{:v,:g,:h,:k,:n,:p,:s,:t => Object}]
|
126
|
+
#
|
127
|
+
def to_hash
|
128
|
+
{
|
129
|
+
v: @v,
|
130
|
+
g: @g,
|
131
|
+
h: @h,
|
132
|
+
k: @k,
|
133
|
+
n: @n,
|
134
|
+
p: @p,
|
135
|
+
s: @s,
|
136
|
+
t: @t
|
137
|
+
}
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# Converts the key back into a DKIM String.
|
142
|
+
#
|
143
|
+
# @return [String]
|
144
|
+
#
|
145
|
+
def to_s
|
146
|
+
tags = []
|
147
|
+
|
148
|
+
tags << "v=#{@v}" if @v
|
149
|
+
tags << "g=#{@g}" if @g
|
150
|
+
tags << "h=#{@h}" if @h
|
151
|
+
tags << "k=#{@k}" if @k
|
152
|
+
tags << "p=#{@p}" if @p
|
153
|
+
tags << "s=#{@s}" if @s
|
154
|
+
tags << "t=#{@t}" if @t
|
155
|
+
tags << "n=#{@n}" if @n
|
156
|
+
|
157
|
+
return tags.join('; ')
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module DKIM
|
2
|
+
module Query
|
3
|
+
#
|
4
|
+
# Represents a unparsable DKIM key.
|
5
|
+
#
|
6
|
+
class MalformedKey
|
7
|
+
|
8
|
+
# Raw value of the DKIM key.
|
9
|
+
#
|
10
|
+
# @return [String]
|
11
|
+
attr_reader :value
|
12
|
+
|
13
|
+
# Cause of the parser failure.
|
14
|
+
#
|
15
|
+
# @return [Parslet::Cause]
|
16
|
+
attr_reader :cause
|
17
|
+
|
18
|
+
#
|
19
|
+
# Initializes the malformed key.
|
20
|
+
#
|
21
|
+
# @param [String] value
|
22
|
+
# The raw DKIM key.
|
23
|
+
#
|
24
|
+
# @param [Parslet::Cause] cause
|
25
|
+
# The cause of the parser failure.
|
26
|
+
#
|
27
|
+
def initialize(value,cause)
|
28
|
+
@value = value
|
29
|
+
@cause = cause
|
30
|
+
end
|
31
|
+
|
32
|
+
alias to_s value
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'parslet'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module DKIM
|
5
|
+
module Query
|
6
|
+
#
|
7
|
+
# DKIM parser.
|
8
|
+
#
|
9
|
+
# @see https://tools.ietf.org/html/rfc6376#section-3
|
10
|
+
#
|
11
|
+
class Parser < Parslet::Parser
|
12
|
+
|
13
|
+
root :record
|
14
|
+
rule(:record) do
|
15
|
+
(
|
16
|
+
fws? >> key_tag >> fws? >>
|
17
|
+
(str(';') >> fws? >> key_tag >> fws?).repeat(0) >> str(';').maybe
|
18
|
+
).as(:tag_list)
|
19
|
+
end
|
20
|
+
|
21
|
+
rule(:key_tag) do
|
22
|
+
(
|
23
|
+
key_v_tag |
|
24
|
+
key_g_tag |
|
25
|
+
key_h_tag |
|
26
|
+
key_k_tag |
|
27
|
+
key_n_tag |
|
28
|
+
key_p_tag |
|
29
|
+
key_s_tag |
|
30
|
+
key_t_tag
|
31
|
+
).as(:tag)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def self.key_tag_rule(name,&block)
|
37
|
+
rule(:"key_#{name}_tag") do
|
38
|
+
str(name).as(:name) >>
|
39
|
+
fws? >> str('=') >> fws? >>
|
40
|
+
instance_eval(&block).as(:value)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def symbol(name)
|
45
|
+
str(name).as(:symbol)
|
46
|
+
end
|
47
|
+
|
48
|
+
public
|
49
|
+
|
50
|
+
key_tag_rule('v') { symbol('DKIM1') }
|
51
|
+
key_tag_rule('g') { key_g_tag_lpart }
|
52
|
+
rule(:key_g_tag_lpart) do
|
53
|
+
(
|
54
|
+
dot_atom_text.maybe >>
|
55
|
+
(str('*') >> dot_atom_text.maybe).maybe
|
56
|
+
).as(:string)
|
57
|
+
end
|
58
|
+
rule(:dot_atom_text) do
|
59
|
+
atext.repeat(1) >> (str('.') >> atext.repeat(1)).repeat(0)
|
60
|
+
end
|
61
|
+
|
62
|
+
key_tag_rule('h') do
|
63
|
+
key_h_tag_alg >> (fws? >> str(':') >> fws? >> key_h_tag_alg).repeat(0)
|
64
|
+
end
|
65
|
+
rule(:key_h_tag_alg) { symbol('sha1') | symbol('sha256') | x_key_h_tag_alg }
|
66
|
+
rule(:x_key_h_tag_alg) { hyphenated_word.as(:string) }
|
67
|
+
|
68
|
+
key_tag_rule('k') { key_k_tag_type }
|
69
|
+
rule(:key_k_tag_type) { symbol('rsa') | x_key_k_tag_type }
|
70
|
+
rule(:x_key_k_tag_type) { hyphenated_word.as(:string) }
|
71
|
+
|
72
|
+
key_tag_rule('n') { qp_section.as(:string) }
|
73
|
+
key_tag_rule('p') { base64string.as(:asn1).maybe }
|
74
|
+
key_tag_rule('s') do
|
75
|
+
key_s_tag_type >> (fws? >> str(':') >> fws? >> key_s_tag_type).repeat(0)
|
76
|
+
end
|
77
|
+
rule(:key_s_tag_type) { symbol('email') | symbol('*') | x_key_s_tag_type }
|
78
|
+
rule(:x_key_s_tag_type) { hyphenated_word.as(:string) }
|
79
|
+
|
80
|
+
key_tag_rule('t') do
|
81
|
+
key_t_tag_flag >> (fws? >> str(':') >> fws? >> key_t_tag_flag).repeat(0)
|
82
|
+
end
|
83
|
+
rule(:key_t_tag_flag) { match['ys'].as(:symbol) | x_key_t_tag_flag }
|
84
|
+
rule(:x_key_t_tag_flag) { hyphenated_word.as(:string) }
|
85
|
+
|
86
|
+
#
|
87
|
+
# Section 2.6: DKIM-Quoted-Printable
|
88
|
+
#
|
89
|
+
rule(:dkim_quoted_printable) do
|
90
|
+
(fws | hex_octet | dkim_safe_char).repeat(0)
|
91
|
+
end
|
92
|
+
rule(:dkim_safe_char) do
|
93
|
+
match['\x21-\x3a'] | str("\x3c") | match['\x3e-\x7e']
|
94
|
+
end
|
95
|
+
|
96
|
+
#
|
97
|
+
# Section 2.4: Common ABNF Tokens
|
98
|
+
#
|
99
|
+
rule(:hyphenated_word) do
|
100
|
+
alpha >> (
|
101
|
+
(str('-').absnt? >> (alpha | digit)) |
|
102
|
+
(str('-').repeat(0) >> (alpha | digit))
|
103
|
+
).repeat(0)
|
104
|
+
end
|
105
|
+
rule(:base64string) do
|
106
|
+
(alpha | digit | str('+') | str('/') | fws).repeat(1) >>
|
107
|
+
(str('=') >> fws? >> (str('=') >> fws?)).maybe
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Section 2.3: Whitespace
|
112
|
+
#
|
113
|
+
rule(:sp) { str(' ') }
|
114
|
+
rule(:crlf) { str("\r\n") }
|
115
|
+
rule(:wsp) { match['\t '] }
|
116
|
+
rule(:wsp?) { wsp.maybe }
|
117
|
+
rule(:lwsp) { (wsp | crlf >> wsp).repeat(0) }
|
118
|
+
rule(:fws) { (wsp.repeat(0) >> crlf).maybe >> wsp.repeat(1) }
|
119
|
+
rule(:fws?) { fws.maybe }
|
120
|
+
|
121
|
+
#
|
122
|
+
# Character rules
|
123
|
+
#
|
124
|
+
rule(:alpha) { match['a-zA-Z'] }
|
125
|
+
rule(:digit) { match['0-9'] }
|
126
|
+
rule(:alnum) { match['a-zA-Z0-9'] }
|
127
|
+
rule(:valchar) { match['\x21-\x3a'] | match['\x3c-\x7e'] }
|
128
|
+
rule(:alnumpunc) { match['a-zA-Z0-9_'] }
|
129
|
+
rule(:atext) { alnum | match['!#$%&\'*+\-/=?^ `{|}~'] }
|
130
|
+
|
131
|
+
#
|
132
|
+
# Quoted printable
|
133
|
+
#
|
134
|
+
rule(:qp_section) do
|
135
|
+
(wsp? >> ptext.repeat(1) >> (wsp >> ptext.repeat(1)).repeat(0)).maybe
|
136
|
+
end
|
137
|
+
rule(:ptext) { hex_octet | safe_char }
|
138
|
+
rule(:safe_char) { match['\x21-\x3c'] | match['\x3e-\x7e'] }
|
139
|
+
rule(:hex_octet) { str('=') >> match['0-9A-F'].repeat(2,2) }
|
140
|
+
|
141
|
+
class Transform < Parslet::Transform
|
142
|
+
|
143
|
+
rule(:symbol => simple(:name)) { name.to_sym }
|
144
|
+
rule(:string => simple(:text)) { text.to_s }
|
145
|
+
# XXX: temporarily disable ASN1 decoding, due to an OpenSSL bug.
|
146
|
+
# rule(:asn1 => simple(:blob)) { OpenSSL::ASN1.decode(blob) }
|
147
|
+
rule(:asn1 => simple(:blob)) { blob }
|
148
|
+
|
149
|
+
rule(tag: {name: simple(:name), value: subtree(:value)}) do
|
150
|
+
{name.to_sym => value}
|
151
|
+
end
|
152
|
+
|
153
|
+
rule(tag_list: subtree(:hashes)) do
|
154
|
+
case hashes
|
155
|
+
when Array then hashes.reduce(&:merge!)
|
156
|
+
else hashes
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# Parses the text into structured data.
|
164
|
+
#
|
165
|
+
# @param [String] text
|
166
|
+
#
|
167
|
+
# @return [Hash]
|
168
|
+
#
|
169
|
+
def self.parse(text)
|
170
|
+
Transform.new.apply(new.parse(text))
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'resolv'
|
2
|
+
|
3
|
+
module DKIM
|
4
|
+
module Query
|
5
|
+
#
|
6
|
+
# Queries the domain for all DKIM selectors.
|
7
|
+
#
|
8
|
+
# @param [String] domain
|
9
|
+
# The domain to query.
|
10
|
+
#
|
11
|
+
# @option options [Array<String>] :selectors
|
12
|
+
# sub-domain selectors.
|
13
|
+
#
|
14
|
+
# @option options [Resolv::DNS] :resolver
|
15
|
+
# Optional resolver to use.
|
16
|
+
#
|
17
|
+
# @return [Hash{String => String}]
|
18
|
+
# The DKIM keys for the domain.
|
19
|
+
#
|
20
|
+
# @api semipublic
|
21
|
+
#
|
22
|
+
def self.query(domain,options={})
|
23
|
+
selectors = options.fetch(:selectors) { selectors_for(domain) }
|
24
|
+
resolver = options.fetch(:resolver) { Resolv::DNS.new }
|
25
|
+
|
26
|
+
keys = {}
|
27
|
+
|
28
|
+
selectors.each do |selector|
|
29
|
+
host = "#{selector}._domainkey.#{domain}"
|
30
|
+
|
31
|
+
begin
|
32
|
+
keys[selector] = resolver.getresource(
|
33
|
+
host, Resolv::DNS::Resource::IN::TXT
|
34
|
+
).strings.join
|
35
|
+
rescue Resolv::ResolvError
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
return keys
|
40
|
+
end
|
41
|
+
|
42
|
+
# Default known selectors
|
43
|
+
SELECTORS = %w[default dkim google s1024 c1211 mandrill]
|
44
|
+
|
45
|
+
#
|
46
|
+
# DKIM query selectors for the host.
|
47
|
+
#
|
48
|
+
# @param [String] host
|
49
|
+
#
|
50
|
+
# @return [Array<String>]
|
51
|
+
#
|
52
|
+
# @api private
|
53
|
+
#
|
54
|
+
def self.selectors_for(host)
|
55
|
+
SELECTORS + [host_without_tld(host)]
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Removes the TLD from the hostname.
|
60
|
+
#
|
61
|
+
# @param [String] host
|
62
|
+
#
|
63
|
+
# @return [String]
|
64
|
+
#
|
65
|
+
# @api private
|
66
|
+
#
|
67
|
+
def self.host_without_tld(host)
|
68
|
+
if host.include?('.') then host[0,host.rindex('.')]
|
69
|
+
else host
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dkim/query/domain'
|
3
|
+
|
4
|
+
describe Domain do
|
5
|
+
let(:domain) { 'yahoo.com' }
|
6
|
+
let(:key) do
|
7
|
+
Key.new(
|
8
|
+
k: :rsa,
|
9
|
+
p: "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrEee0Ri4Juz+QfiWYui/E9UGSXau/2P8LjnTD8V4Unn+2FAZVGE3kL23bzeoULYv4PeleB3gfmJiDJOKU3Ns5L4KJAUUHjFwDebt0NP+sBK0VKeTATL2Yr/S3bT/xhy+1xtj4RkdV7fVxTn56Lb4udUnwuxK4V5b5PdOKj/+XcwIDAQAB",
|
10
|
+
n: "A 1024 bit key;",
|
11
|
+
)
|
12
|
+
end
|
13
|
+
let(:selector) { 's1024' }
|
14
|
+
let(:keys) { {selector => key} }
|
15
|
+
|
16
|
+
subject { described_class.new(domain,keys) }
|
17
|
+
|
18
|
+
describe "#initialize" do
|
19
|
+
it "should set name" do
|
20
|
+
expect(subject.name).to be domain
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should set keys" do
|
24
|
+
expect(subject.keys).to be keys
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe ".parse" do
|
29
|
+
let(:key) do
|
30
|
+
%{k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrEee0Ri4Juz+QfiWYui/E9UGSXau/2P8LjnTD8V4Unn+2FAZVGE3kL23bzeoULYv4PeleB3gfmJiDJOKU3Ns5L4KJAUUHjFwDebt0NP+sBK0VKeTATL2Yr/S3bT/xhy+1xtj4RkdV7fVxTn56Lb4udUnwuxK4V5b5PdOKj/+XcwIDAQAB; n=A 1024 bit key}
|
31
|
+
end
|
32
|
+
let(:keys) { {selector => key} }
|
33
|
+
|
34
|
+
subject { described_class.parse(domain,keys) }
|
35
|
+
|
36
|
+
it "should parse the keys" do
|
37
|
+
expect(subject.keys[selector]).to be_kind_of(Key)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe ".query" do
|
42
|
+
subject { described_class.query(domain) }
|
43
|
+
|
44
|
+
it "should find all known keys" do
|
45
|
+
expect(subject.keys).to have_key('s1024')
|
46
|
+
end
|
47
|
+
|
48
|
+
context "with custom selectors" do
|
49
|
+
let(:selectors) { ['google', 's1024'] }
|
50
|
+
|
51
|
+
subject { described_class.query(domain, selectors: selectors) }
|
52
|
+
|
53
|
+
it "should query those selectors only" do
|
54
|
+
expect(subject.keys).to have_key('s1024')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "with no selectors" do
|
59
|
+
let(:selectors) { [] }
|
60
|
+
|
61
|
+
subject { described_class.query(domain, selectors: selectors) }
|
62
|
+
|
63
|
+
it "should not find any keys" do
|
64
|
+
expect(subject.keys).to be_empty
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "#each" do
|
70
|
+
context "when given a block" do
|
71
|
+
it "should yield each Key" do
|
72
|
+
expect { |b| subject.each(&b) }.to yield_with_args(key)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "when not given a block" do
|
77
|
+
it "should return an Enumerator" do
|
78
|
+
expect(subject.each).to be_kind_of(Enumerator)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "#[]" do
|
84
|
+
context "when given a valid selector" do
|
85
|
+
it "should return the key" do
|
86
|
+
expect(subject[selector]).to be key
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "when given an unknown selector" do
|
91
|
+
it "should return nil" do
|
92
|
+
expect(subject['foo']).to be_nil
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dkim/query/key'
|
3
|
+
|
4
|
+
describe Key do
|
5
|
+
let(:k) { :rsa }
|
6
|
+
let(:p) { "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrEee0Ri4Juz+QfiWYui/E9UGSXau/2P8LjnTD8V4Unn+2FAZVGE3kL23bzeoULYv4PeleB3gfmJiDJOKU3Ns5L4KJAUUHjFwDebt0NP+sBK0VKeTATL2Yr/S3bT/xhy+1xtj4RkdV7fVxTn56Lb4udUnwuxK4V5b5PdOKj/+XcwIDAQAB" }
|
7
|
+
let(:t) { :s }
|
8
|
+
let(:n) { "A 1024 bit key;" }
|
9
|
+
|
10
|
+
subject do
|
11
|
+
described_class.new(
|
12
|
+
k: k,
|
13
|
+
p: p,
|
14
|
+
t: t,
|
15
|
+
n: n
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#initialize" do
|
20
|
+
it "should set @k" do
|
21
|
+
expect(subject.k).to be k
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should set @p" do
|
25
|
+
expect(subject.p).to be p
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should set @t" do
|
29
|
+
expect(subject.t).to be t
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should set @n" do
|
33
|
+
expect(subject.n).to be n
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
let(:record) { %{k=#{k}; p=#{p}; t=#{t}; n=#{n}} }
|
38
|
+
let(:invalid_record) { "v=spf1" }
|
39
|
+
|
40
|
+
describe ".parse!" do
|
41
|
+
context "when parsing a valid DKIM Key record" do
|
42
|
+
subject { described_class.parse!(record) }
|
43
|
+
|
44
|
+
it "should return a Key" do
|
45
|
+
expect(subject).to be_kind_of(described_class)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should parse k" do
|
49
|
+
expect(subject.k).to be k
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should parse p" do
|
53
|
+
expect(subject.p).to be == p
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should parse t" do
|
57
|
+
expect(subject.t).to be t
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should parse n" do
|
61
|
+
expect(subject.n).to be == n
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when parsing an invalid DKIM Key record" do
|
66
|
+
it "should raise an InvalidKey exception" do
|
67
|
+
expect {
|
68
|
+
described_class.parse!(invalid_record)
|
69
|
+
}.to raise_error(InvalidKey)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe ".parse" do
|
75
|
+
context "when parsing a valid DKIM Key record" do
|
76
|
+
subject { described_class.parse(record) }
|
77
|
+
|
78
|
+
it "should return a Key" do
|
79
|
+
expect(subject).to be_kind_of(described_class)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "when parsing an invalid DKIM Key record" do
|
84
|
+
subject { described_class.parse(invalid_record) }
|
85
|
+
|
86
|
+
it "should return a MalformedKey" do
|
87
|
+
expect(subject).to be_kind_of(MalformedKey)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "#to_hash" do
|
93
|
+
subject { super().to_hash }
|
94
|
+
|
95
|
+
it "should include :k" do
|
96
|
+
expect(subject[:k]).to be == k
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should include :p" do
|
100
|
+
expect(subject[:p]).to be == p
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
it "should include :t" do
|
105
|
+
expect(subject[:t]).to be == t
|
106
|
+
end
|
107
|
+
it "should include :n" do
|
108
|
+
expect(subject[:n]).to be == n
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "#to_s" do
|
113
|
+
it "should return a semicolon deliminited string" do
|
114
|
+
expect(subject.to_s).to be == "k=#{k}; p=#{p}; t=#{t}; n=#{n}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'dkim/query/malformed_key'
|
3
|
+
|
4
|
+
describe MalformedKey do
|
5
|
+
describe "#to_s" do
|
6
|
+
let(:value) { "foo bar" }
|
7
|
+
let(:cause) { double(:parslet_error) }
|
8
|
+
|
9
|
+
subject { described_class.new(value,cause) }
|
10
|
+
|
11
|
+
it "should return the value" do
|
12
|
+
expect(subject.to_s).to be == value
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|