sslackey 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|