sslackey 0.6.0
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.
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +46 -0
- data/Rakefile +9 -0
- data/examples/README.md +11 -0
- data/examples/cacert.pem +1570 -0
- data/examples/simple.rb +49 -0
- data/lib/sslackey/authority_checker.rb +107 -0
- data/lib/sslackey/cache/redis_revocation_cache.rb +30 -0
- data/lib/sslackey/revocation_checker.rb +90 -0
- data/lib/sslackey/version.rb +3 -0
- data/lib/sslackey.rb +3 -0
- data/spec/authority_checker_spec.rb +148 -0
- data/spec/cache/redis_revocation_cache_spec.rb +61 -0
- data/spec/fixtures/AkamaiSub3.crl +0 -0
- data/spec/fixtures/cacert.pem +696 -0
- data/spec/fixtures/crl_only_cert.pem +18 -0
- data/spec/fixtures/ocsp_enabled_cert.pem +35 -0
- data/spec/fixtures/sample_certificate_revocation_list.crl +0 -0
- data/spec/fixtures/sample_ocsp_response.der +0 -0
- data/spec/fixtures/ssl.rb +29 -0
- data/spec/revocation_checker_spec.rb +113 -0
- data/spec/spec_helper.rb +22 -0
- data/sslackey.gemspec +35 -0
- metadata +227 -0
data/examples/simple.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'net/https'
|
2
|
+
require 'uri'
|
3
|
+
require 'logger'
|
4
|
+
require 'openssl'
|
5
|
+
require 'sslackey'
|
6
|
+
|
7
|
+
|
8
|
+
module OpenSSL
|
9
|
+
module SSL
|
10
|
+
class SSLSocket
|
11
|
+
def post_connection_check(hostname)
|
12
|
+
unless OpenSSL::SSL.verify_certificate_identity(peer_cert, hostname)
|
13
|
+
raise SSLError, "hostname was not match with the server certificate"
|
14
|
+
end
|
15
|
+
|
16
|
+
checker = RevocationChecker.new()
|
17
|
+
status = checker.check_revocation_status(peer_cert)
|
18
|
+
raise SSLError, "Bad revocation status: #{status}" unless status == :successful
|
19
|
+
|
20
|
+
return true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
RevocationChecker.setup File.join(File.dirname(__FILE__), 'cacert.pem')
|
27
|
+
RevocationChecker.cache = RedisRevocationCache.new("localhost", "6379")
|
28
|
+
|
29
|
+
#Test the connection
|
30
|
+
LOGGER = Logger.new(STDERR)
|
31
|
+
|
32
|
+
# tdameritrade.com is broken on ocsp parsing
|
33
|
+
# americanexpress.com : requires CRL check
|
34
|
+
|
35
|
+
url = URI.parse('https://www.google.com ')
|
36
|
+
|
37
|
+
http = Net::HTTP.new(url.host, url.port)
|
38
|
+
http.set_debug_output $stderr
|
39
|
+
http.use_ssl=true
|
40
|
+
store = OpenSSL::X509::Store.new
|
41
|
+
store.add_file File.join(File.dirname(__FILE__), 'cacert.pem')
|
42
|
+
http.cert_store = store
|
43
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
44
|
+
|
45
|
+
http.start() do |http|
|
46
|
+
request = Net::HTTP::Get.new url.request_uri
|
47
|
+
response = http.request request # Net::HTTPResponse object
|
48
|
+
puts response
|
49
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
class AuthorityChecker
|
2
|
+
REVOCATION_RESPONSES = [:successful, :unknown, :revoked]
|
3
|
+
|
4
|
+
attr_accessor :trusted_certs_file_path
|
5
|
+
|
6
|
+
def initialize(trusted_certs_path)
|
7
|
+
@trusted_certs_file_path = trusted_certs_path
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate(certificate, issuer_certificate)
|
12
|
+
ocsp_url = nil
|
13
|
+
crl_url = nil
|
14
|
+
|
15
|
+
certificate.extensions.each do |extension|
|
16
|
+
props = extension.to_h
|
17
|
+
if props["oid"] == "authorityInfoAccess"
|
18
|
+
ocsp_url = AuthorityChecker.parse_authority_info_access(props["value"])
|
19
|
+
end
|
20
|
+
|
21
|
+
if props["oid"] == "crlDistributionPoints"
|
22
|
+
crl_url = AuthorityChecker.parse_crl_distribution_points(props["value"])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
if ocsp_url
|
26
|
+
response = perform_ocsp_check(certificate, issuer_certificate, ocsp_url)
|
27
|
+
elsif crl_url
|
28
|
+
response = perform_crl_check(certificate, crl_url)
|
29
|
+
else
|
30
|
+
raise "Could not find valid oscp or crl extension to check against in certificate #{certificate.subject}"
|
31
|
+
end
|
32
|
+
|
33
|
+
raise "Unknown revocation response #{response}" unless AuthorityChecker::REVOCATION_RESPONSES.include?(response)
|
34
|
+
|
35
|
+
response
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.parse_authority_info_access(ocsp_string)
|
39
|
+
ocsp_string.each_line do |line|
|
40
|
+
if line.index(/OCSP/)
|
41
|
+
urls = line.scan(/URI:.*/)
|
42
|
+
url = urls[0]
|
43
|
+
url.slice!(/URI:/)
|
44
|
+
return url
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.parse_crl_distribution_points(crl_string)
|
50
|
+
crl_string.each_line do |line|
|
51
|
+
urls = line.scan(/URI:.*/)
|
52
|
+
crl_url = urls[0]
|
53
|
+
crl_url.slice!(/URI:/)
|
54
|
+
return crl_url
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def perform_ocsp_check(certificate, issuer_certificate, ocsp_url)
|
60
|
+
certificate_file = write_certificate_file(certificate, "provider")
|
61
|
+
issuer_file = write_certificate_file(issuer_certificate, "issuer")
|
62
|
+
output_file = create_response_file("ocsp_output")
|
63
|
+
|
64
|
+
generate_ocsp_response(issuer_file.path, certificate_file.path, output_file.path, ocsp_url)
|
65
|
+
|
66
|
+
read_ocsp_response(output_file).to_sym
|
67
|
+
end
|
68
|
+
|
69
|
+
def generate_ocsp_response(issuer_file_path, certificate_file_path, output_file_path, ocsp_url)
|
70
|
+
`openssl ocsp -no_nonce -CAfile #{trusted_certs_file_path} -issuer #{issuer_file_path} -cert #{certificate_file_path} -respout #{output_file_path} -url #{ocsp_url}`
|
71
|
+
end
|
72
|
+
|
73
|
+
def read_ocsp_response(output_file)
|
74
|
+
output_file.rewind
|
75
|
+
response = OpenSSL::OCSP::Response.new(output_file.read)
|
76
|
+
output_file.close
|
77
|
+
response.status_string
|
78
|
+
end
|
79
|
+
|
80
|
+
def write_certificate_file(certificate, file_name)
|
81
|
+
file = Tempfile.new(file_name)
|
82
|
+
file.write(certificate)
|
83
|
+
file.rewind
|
84
|
+
file.close
|
85
|
+
file
|
86
|
+
end
|
87
|
+
|
88
|
+
def create_response_file(file_name)
|
89
|
+
Tempfile.new(file_name)
|
90
|
+
end
|
91
|
+
|
92
|
+
def perform_crl_check(certificate, crl_url)
|
93
|
+
content = fetch_crl_content(crl_url)
|
94
|
+
crl = OpenSSL::X509::CRL.new(content)
|
95
|
+
|
96
|
+
revoked_matches = crl.revoked.select { |elem| elem.serial == certificate.serial }
|
97
|
+
|
98
|
+
return :revoked unless revoked_matches.empty?
|
99
|
+
|
100
|
+
:successful
|
101
|
+
end
|
102
|
+
|
103
|
+
def fetch_crl_content(crl_url)
|
104
|
+
`curl -s '#{crl_url}'`
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'redis'
|
2
|
+
require 'redis/namespace'
|
3
|
+
require 'active_support/all'
|
4
|
+
|
5
|
+
class RedisRevocationCache
|
6
|
+
|
7
|
+
attr_accessor :redis, :expiration_seconds
|
8
|
+
|
9
|
+
def initialize(redis_host, redis_port)
|
10
|
+
@redis = Redis::Namespace.new(:revocation, :redis => Redis.new(:host => redis_host, :port => redis_port, :threadsafe => true))
|
11
|
+
@expiration_seconds = 3600
|
12
|
+
end
|
13
|
+
|
14
|
+
def cached_response(certificate)
|
15
|
+
response = redis.get(get_key(certificate))
|
16
|
+
LOGGER.info("got a cached response: #{response}") if response && defined?(LOGGER)
|
17
|
+
response.try(:to_sym)
|
18
|
+
end
|
19
|
+
|
20
|
+
def cache_response(certificate, response)
|
21
|
+
key = get_key(certificate)
|
22
|
+
LOGGER.info "caching revocation response for certificate: #{certificate.subject}" if defined?(LOGGER)
|
23
|
+
redis.set(key, response)
|
24
|
+
redis.expire(key, expiration_seconds)
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_key(certificate)
|
28
|
+
certificate.subject.hash
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# Load all known issuer certs into memory. Key by cert name
|
2
|
+
require 'logger'
|
3
|
+
require 'openssl'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'redis'
|
6
|
+
require 'redis/namespace'
|
7
|
+
require 'active_support/all'
|
8
|
+
|
9
|
+
class RevocationChecker
|
10
|
+
@issuers = {}
|
11
|
+
@issuers_by_name = {}
|
12
|
+
@trusted_certs_file_path = nil
|
13
|
+
@cache = nil
|
14
|
+
|
15
|
+
class << self
|
16
|
+
attr_accessor :issuers, :issuers_by_name, :trusted_certs_file_path, :cache
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.setup(trusted_certs_file_path)
|
20
|
+
RevocationChecker.issuers = {}
|
21
|
+
RevocationChecker.issuers_by_name = {}
|
22
|
+
|
23
|
+
RevocationChecker.trusted_certs_file_path = trusted_certs_file_path
|
24
|
+
|
25
|
+
certs_file = File.read(RevocationChecker.trusted_certs_file_path)
|
26
|
+
|
27
|
+
certs = certs_file.scan(/-----BEGIN CERTIFICATE-----[^-]*-----END CERTIFICATE-----/)
|
28
|
+
|
29
|
+
certs.each do |cert|
|
30
|
+
certificate = OpenSSL::X509::Certificate.new(cert)
|
31
|
+
|
32
|
+
certificate.extensions.each do |extension|
|
33
|
+
props = extension.to_h
|
34
|
+
if props["oid"] == "subjectKeyIdentifier"
|
35
|
+
issuer_key = props["value"]
|
36
|
+
RevocationChecker.issuers[issuer_key] = certificate
|
37
|
+
end
|
38
|
+
end
|
39
|
+
RevocationChecker.issuers_by_name[certificate.subject.hash] = certificate
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def check_revocation_status(certificate)
|
44
|
+
|
45
|
+
unless RevocationChecker.cache
|
46
|
+
LOGGER.info("skipping revocation caching") if defined? LOGGER
|
47
|
+
return get_latest_revocation_status(certificate)
|
48
|
+
end
|
49
|
+
|
50
|
+
if cached_response = RevocationChecker.cache.cached_response(certificate)
|
51
|
+
return cached_response
|
52
|
+
end
|
53
|
+
|
54
|
+
response = get_latest_revocation_status(certificate)
|
55
|
+
|
56
|
+
RevocationChecker.cache.cache_response(certificate, response)
|
57
|
+
|
58
|
+
response
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def get_latest_revocation_status(certificate)
|
63
|
+
issuer_certificate = nil
|
64
|
+
certificate.extensions.each do |extension|
|
65
|
+
props = extension.to_h
|
66
|
+
if props["oid"] == "authorityKeyIdentifier"
|
67
|
+
issuer_key = RevocationChecker.parse_authority_key_identifier(props["value"])
|
68
|
+
issuer_certificate = RevocationChecker.issuers[issuer_key]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
unless issuer_certificate
|
73
|
+
issuer_certificate = RevocationChecker.issuers_by_name[certificate.issuer.hash]
|
74
|
+
end
|
75
|
+
|
76
|
+
raise "No issuer certificate #{certificate.issuer} found for certificate #{certificate.subject}" unless issuer_certificate
|
77
|
+
|
78
|
+
real_time_checker = AuthorityChecker.new(RevocationChecker.trusted_certs_file_path)
|
79
|
+
response = real_time_checker.validate(certificate, issuer_certificate)
|
80
|
+
|
81
|
+
response
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.parse_authority_key_identifier(authority_key_identifier_string)
|
85
|
+
authority_key_identifier_string.slice!(/keyid:/)
|
86
|
+
authority_key_identifier_string.slice!(/\n/)
|
87
|
+
authority_key_identifier_string
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
data/lib/sslackey.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'openssl'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'uri'
|
5
|
+
require 'lib/sslackey/authority_checker'
|
6
|
+
|
7
|
+
def load_ocsp_enabled_cert
|
8
|
+
ocsp_enabled_cert = File.read(File.expand_path "../fixtures/ocsp_enabled_cert.pem", __FILE__)
|
9
|
+
OpenSSL::X509::Certificate.new(ocsp_enabled_cert)
|
10
|
+
end
|
11
|
+
|
12
|
+
def load_non_ocsp_cert
|
13
|
+
crl_only_cert = File.read(File.expand_path "../fixtures/crl_only_cert.pem", __FILE__)
|
14
|
+
OpenSSL::X509::Certificate.new(crl_only_cert)
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_sample_ocsp_response
|
18
|
+
File.open(File.expand_path "../fixtures/sample_ocsp_response.der", __FILE__)
|
19
|
+
end
|
20
|
+
|
21
|
+
def load_crl_without_cert_revoked
|
22
|
+
File.read(File.expand_path "../fixtures/AkamaiSub3.crl", __FILE__)
|
23
|
+
end
|
24
|
+
|
25
|
+
def load_crl_with_cert_revoked
|
26
|
+
File.read(File.expand_path "../fixtures/sample_certificate_revocation_list.crl", __FILE__)
|
27
|
+
end
|
28
|
+
|
29
|
+
describe AuthorityChecker do
|
30
|
+
|
31
|
+
describe ".parse_authority_info_access" do
|
32
|
+
|
33
|
+
context "with a multi line authority info string" do
|
34
|
+
it "only finds the value that matches OCSP" do
|
35
|
+
ocsp_string = "CA Issuers - URI:http://crt.usertrust.com/USERTrustLegacySecureServerCA.crt\nOCSP - URI:http://ocsp.usertrust.com\n"
|
36
|
+
AuthorityChecker.parse_authority_info_access(ocsp_string).should == "http://ocsp.usertrust.com"
|
37
|
+
|
38
|
+
ocsp_string = "OCSP - URI:http://ocsp.verisign.com\nCA Issuers - URI:http://SVRIntl-G3-aia.verisign.com/SVRIntlG3.cer\n"
|
39
|
+
AuthorityChecker.parse_authority_info_access(ocsp_string).should == "http://ocsp.verisign.com"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with a single authority info line" do
|
44
|
+
it "finds the right value" do
|
45
|
+
ocsp_string = "OCSP - URI:http ://ocsp.verisign.com"
|
46
|
+
AuthorityChecker.parse_authority_info_access(ocsp_string).should == "http ://ocsp.verisign.com"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe ".parse_crl_distribution_points" do
|
52
|
+
context 'with a valid crl info string' do
|
53
|
+
it "finds a matching crl url" do
|
54
|
+
crl_string = "URI:http://crl.globalsign.net/AkamaiSub3.crl\n"
|
55
|
+
AuthorityChecker.parse_crl_distribution_points(crl_string).should == "http://crl.globalsign.net/AkamaiSub3.crl"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#validate" do
|
61
|
+
before do
|
62
|
+
@authority_checker = AuthorityChecker.new(nil)
|
63
|
+
end
|
64
|
+
context "when ocsp info present" do
|
65
|
+
it "uses ocsp strategy to verify certificate" do
|
66
|
+
AuthorityChecker.expects(:parse_authority_info_access).returns "ocsp.verisign.com"
|
67
|
+
AuthorityChecker.stubs(:parse_crl_distribution_points)
|
68
|
+
cert = load_ocsp_enabled_cert
|
69
|
+
AuthorityChecker.any_instance.expects(:perform_ocsp_check).with(cert, "stub issuer cert", "ocsp.verisign.com").returns :successful
|
70
|
+
@authority_checker.validate(cert, "stub issuer cert").should == :successful
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "when only crl info present" do
|
75
|
+
it "falls back to the crl strategy to verify the certificate" do
|
76
|
+
AuthorityChecker.stubs(:parse_authority_info_access)
|
77
|
+
AuthorityChecker.expects(:parse_crl_distribution_points).returns "crl.verisign.com"
|
78
|
+
cert = load_non_ocsp_cert
|
79
|
+
AuthorityChecker.any_instance.expects(:perform_crl_check).with(cert, "crl.verisign.com").returns :successful
|
80
|
+
@authority_checker.validate(cert, "stub issuer certificate").should == :successful
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "when neither crl or ocsp info is in the certificate" do
|
85
|
+
it "blows up" do
|
86
|
+
AuthorityChecker.stubs(:parse_crl_distribution_points)
|
87
|
+
AuthorityChecker.any_instance.stubs(:perform_crl_check)
|
88
|
+
|
89
|
+
cert = load_non_ocsp_cert
|
90
|
+
expect { @authority_checker.validate(cert, "stub issuer cert") }.to raise_error(/Could not find valid oscp or crl extension/)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe "#perform_crl_check" do
|
96
|
+
it "returns a status of revoked when the certificate is on the crl" do
|
97
|
+
crl_response = load_crl_with_cert_revoked
|
98
|
+
AuthorityChecker.any_instance.expects(:fetch_crl_content).returns(crl_response)
|
99
|
+
checker = AuthorityChecker.new(nil)
|
100
|
+
|
101
|
+
cert = load_ocsp_enabled_cert
|
102
|
+
checker.perform_crl_check(cert, nil).should == :revoked
|
103
|
+
end
|
104
|
+
|
105
|
+
it "returns a status of successful when the certificate is not on the crl" do
|
106
|
+
crl_response = load_crl_without_cert_revoked
|
107
|
+
AuthorityChecker.any_instance.expects(:fetch_crl_content).returns(crl_response)
|
108
|
+
checker = AuthorityChecker.new(nil)
|
109
|
+
|
110
|
+
cert = load_ocsp_enabled_cert
|
111
|
+
checker.perform_crl_check(cert, "crl url").should == :successful
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "#perform_ocsp_check" do
|
116
|
+
it "writes certificates to files and invokes open ssl verifier" do
|
117
|
+
cert = mock()
|
118
|
+
cert.expects(:path).returns "cert path"
|
119
|
+
issuer_cert = mock()
|
120
|
+
issuer_cert.expects(:path).returns "issuer path"
|
121
|
+
response = mock()
|
122
|
+
response.expects(:path).returns "response path"
|
123
|
+
|
124
|
+
AuthorityChecker.any_instance.expects(:write_certificate_file).with(cert, "provider").returns cert
|
125
|
+
AuthorityChecker.any_instance.expects(:write_certificate_file).with(issuer_cert, "issuer").returns issuer_cert
|
126
|
+
AuthorityChecker.any_instance.expects(:create_response_file).returns(response).returns response
|
127
|
+
AuthorityChecker.any_instance.expects(:read_ocsp_response).returns 'successful'
|
128
|
+
|
129
|
+
AuthorityChecker.any_instance.expects(:generate_ocsp_response).with("issuer path", "cert path", "response path", "ocsp.verisign.com")
|
130
|
+
|
131
|
+
checker = AuthorityChecker.new(nil)
|
132
|
+
|
133
|
+
checker.perform_ocsp_check(cert, issuer_cert, "ocsp.verisign.com").should == :successful
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "#read_ocsp_response" do
|
138
|
+
context "when a valid response is written to a file" do
|
139
|
+
it "parses and reads a successful ocsp response correctly" do
|
140
|
+
checker = AuthorityChecker.new(nil)
|
141
|
+
checker.read_ocsp_response(load_sample_ocsp_response).should == 'successful'
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
end
|
148
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'redis'
|
3
|
+
require 'redis/namespace'
|
4
|
+
require 'active_support/all'
|
5
|
+
require 'lib/sslackey/cache/redis_revocation_cache'
|
6
|
+
|
7
|
+
describe RedisRevocationCache do
|
8
|
+
|
9
|
+
describe "#initialize" do
|
10
|
+
it "creates a new Redis with the correct host and port" do
|
11
|
+
Redis::Namespace.expects(:new).returns "a redis namespace"
|
12
|
+
Redis.expects(:new).with(:host => "redis.test.com", :port => 80, :threadsafe => true).returns "a redis"
|
13
|
+
RedisRevocationCache.new("redis.test.com", 80)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context "caching methods" do
|
18
|
+
before do
|
19
|
+
Redis::Namespace.stubs(:new).returns nil
|
20
|
+
@redis = mock()
|
21
|
+
@cache_service = RedisRevocationCache.new("some host", 90)
|
22
|
+
@cache_service.redis = @redis
|
23
|
+
end
|
24
|
+
describe "#cached_response" do
|
25
|
+
it "gets the symbolized response from redis" do
|
26
|
+
@redis.expects(:get).with("12345").returns "successful"
|
27
|
+
@cache_service.expects(:get_key).returns "12345"
|
28
|
+
@cache_service.cached_response("some cert").should == :successful
|
29
|
+
end
|
30
|
+
|
31
|
+
it "returns nil response when no response cached" do
|
32
|
+
@redis.expects(:get).with("12345").returns nil
|
33
|
+
@cache_service.expects(:get_key).returns "12345"
|
34
|
+
@cache_service.cached_response("some cert").should be_nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#cache_response" do
|
39
|
+
it "sets the key in redis along with an expiration time" do
|
40
|
+
cert = mock()
|
41
|
+
cert.stubs(:subject)
|
42
|
+
@redis.expects(:set).with("12345","successful").returns nil
|
43
|
+
@redis.expects(:expire).with("12345", @cache_service.expiration_seconds)
|
44
|
+
@cache_service.expects(:get_key).returns "12345"
|
45
|
+
@cache_service.cache_response(cert,"successful")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "#get_key" do
|
50
|
+
it "uses the hash of the certificate subject name as the caching key" do
|
51
|
+
cert = mock()
|
52
|
+
cert.stubs(:subject)
|
53
|
+
subject = mock()
|
54
|
+
cert.expects(:subject).returns subject
|
55
|
+
subject.expects(:hash).returns "12345"
|
56
|
+
@cache_service.get_key(cert).should == "12345"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
Binary file
|