r509-ocsp-responder 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +112 -17
- data/doc/R509.html +6 -6
- data/doc/R509/Ocsp.html +10 -10
- data/doc/R509/Ocsp/Helper.html +9 -9
- data/doc/R509/Ocsp/Helper/RequestChecker.html +73 -73
- data/doc/R509/Ocsp/Helper/ResponseSigner.html +59 -59
- data/doc/R509/Ocsp/Responder.html +10 -10
- data/doc/R509/Ocsp/Responder/OcspConfig.html +31 -31
- data/doc/R509/Ocsp/Responder/Server.html +9 -9
- data/doc/R509/Ocsp/Responder/StatusError.html +9 -9
- data/doc/R509/Ocsp/Signer.html +36 -44
- data/doc/_index.html +23 -23
- data/doc/class_list.html +2 -2
- data/doc/css/style.css +10 -0
- data/doc/file.README.html +120 -28
- data/doc/file_list.html +1 -1
- data/doc/frames.html +1 -1
- data/doc/index.html +120 -28
- data/doc/js/full_list.js +6 -1
- data/doc/method_list.html +28 -56
- data/doc/top-level-namespace.html +5 -5
- data/lib/r509/ocsp/responder/ocsp-config.rb +27 -27
- data/lib/r509/ocsp/responder/server.rb +129 -131
- data/lib/r509/ocsp/responder/version.rb +4 -4
- data/lib/r509/ocsp/signer.rb +219 -219
- data/spec/fixtures.rb +145 -190
- data/spec/fixtures/test_ca_ec.cer +14 -0
- data/spec/fixtures/test_ca_ec.key +6 -0
- data/spec/server_spec.rb +405 -397
- data/spec/signer_spec.rb +262 -249
- data/spec/spec_helper.rb +2 -2
- metadata +10 -8
@@ -1,8 +1,6 @@
|
|
1
|
-
require 'rubygems' if RUBY_VERSION < "1.9"
|
2
1
|
require 'sinatra/base'
|
3
2
|
require 'r509'
|
4
3
|
require 'r509/ocsp/signer'
|
5
|
-
require 'r509/validity/redis'
|
6
4
|
require 'base64'
|
7
5
|
require 'dependo'
|
8
6
|
require 'logger'
|
@@ -13,157 +11,157 @@ require File.dirname(__FILE__)+'/ocsp-config.rb'
|
|
13
11
|
# I'd rather use HUP, but daemons like thin already capture that
|
14
12
|
# so we can't use it.
|
15
13
|
Signal.trap("USR2") do
|
16
|
-
|
17
|
-
|
14
|
+
R509::OCSP::Responder::OCSPConfig.load_config
|
15
|
+
R509::OCSP::Responder::OCSPConfig.print_config
|
18
16
|
end
|
19
17
|
|
20
18
|
|
21
|
-
module R509::
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
class Server < Sinatra::Base
|
27
|
-
include Dependo::Mixin
|
19
|
+
module R509::OCSP::Responder
|
20
|
+
#error for status checking
|
21
|
+
class StatusError < StandardError
|
22
|
+
end
|
28
23
|
|
29
|
-
|
30
|
-
|
31
|
-
disable :protection #disable Rack::Protection (for speed)
|
32
|
-
disable :logging
|
33
|
-
set :environment, :production
|
34
|
-
end
|
24
|
+
class Server < Sinatra::Base
|
25
|
+
include Dependo::Mixin
|
35
26
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
27
|
+
configure do
|
28
|
+
mime_type :ocsp, 'application/ocsp-response'
|
29
|
+
disable :protection #disable Rack::Protection (for speed)
|
30
|
+
disable :logging
|
31
|
+
set :environment, :production
|
32
|
+
end
|
41
33
|
|
42
|
-
|
43
|
-
|
44
|
-
|
34
|
+
error do
|
35
|
+
log.error env["sinatra.error"].inspect
|
36
|
+
log.error env["sinatra.error"].backtrace.join("\n")
|
37
|
+
"Something is amiss with our OCSP responder. You should ... wait?"
|
38
|
+
end
|
45
39
|
|
46
|
-
|
47
|
-
|
48
|
-
|
40
|
+
error OpenSSL::OCSP::OCSPError do
|
41
|
+
"Invalid request"
|
42
|
+
end
|
49
43
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
44
|
+
error R509::OCSP::Responder::StatusError do
|
45
|
+
"Down"
|
46
|
+
end
|
54
47
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
else
|
60
|
-
raise R509::Ocsp::Responder::StatusError
|
61
|
-
end
|
62
|
-
rescue
|
63
|
-
raise R509::Ocsp::Responder::StatusError
|
64
|
-
end
|
65
|
-
end
|
48
|
+
get '/favicon.ico' do
|
49
|
+
log.debug "go away. no children."
|
50
|
+
"go away. no children"
|
51
|
+
end
|
66
52
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
request_response = handle_ocsp_request(der, "GET")
|
74
|
-
build_headers(request_response)
|
75
|
-
request_response[:response].to_der
|
53
|
+
get '/status/?' do
|
54
|
+
begin
|
55
|
+
if Dependo::Registry[:ocsp_signer].validity_checker.is_available?
|
56
|
+
"OK"
|
57
|
+
else
|
58
|
+
raise R509::OCSP::Responder::StatusError
|
76
59
|
end
|
60
|
+
rescue
|
61
|
+
raise R509::OCSP::Responder::StatusError
|
62
|
+
end
|
63
|
+
end
|
77
64
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
65
|
+
get '/*' do
|
66
|
+
raw_request = params[:splat].join("/")
|
67
|
+
#remove any leading slashes (looking at you MS Crypto API)
|
68
|
+
raw_request.sub!(/^\/+/,"")
|
69
|
+
log.info { "GET Request: "+raw_request }
|
70
|
+
der = Base64.decode64(raw_request)
|
71
|
+
request_response = handle_ocsp_request(der, "GET")
|
72
|
+
build_headers(request_response)
|
73
|
+
request_response[:response].to_der
|
74
|
+
end
|
86
75
|
|
87
|
-
|
76
|
+
post '/' do
|
77
|
+
if request.media_type == 'application/ocsp-request'
|
78
|
+
der = request.env["rack.input"].read
|
79
|
+
log.info { "POST Request: "+Base64.encode64(der).gsub!(/\n/,"") }
|
80
|
+
request_response = handle_ocsp_request(der, "POST")
|
81
|
+
request_response[:response].to_der
|
82
|
+
end
|
83
|
+
end
|
88
84
|
|
89
|
-
|
90
|
-
begin
|
91
|
-
request_response = ocsp_signer.handle_request(der)
|
85
|
+
private
|
92
86
|
|
93
|
-
|
87
|
+
def handle_ocsp_request(der, method)
|
88
|
+
begin
|
89
|
+
request_response = ocsp_signer.handle_request(der)
|
94
90
|
|
95
|
-
|
96
|
-
request_response
|
97
|
-
rescue StandardError => e
|
98
|
-
log.error "unexpected error #{e}"
|
99
|
-
raise e
|
100
|
-
end
|
101
|
-
end
|
91
|
+
log_ocsp_response(request_response[:response],method)
|
102
92
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
93
|
+
content_type :ocsp
|
94
|
+
request_response
|
95
|
+
rescue StandardError => e
|
96
|
+
log.error "unexpected error #{e}"
|
97
|
+
raise e
|
98
|
+
end
|
99
|
+
end
|
108
100
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
when OpenSSL::OCSP::RESPONSE_STATUS_UNAUTHORIZED
|
140
|
-
log.info { "#{method} Request For Unauthorized CA. UserAgent: #{env["HTTP_USER_AGENT"]}" }
|
141
|
-
when OpenSSL::OCSP::RESPONSE_STATUS_MALFORMEDREQUEST
|
142
|
-
log.info { "#{method} Malformed Request. UserAgent: #{env["HTTP_USER_AGENT"]}" }
|
101
|
+
def log_ocsp_response(ocsp_response, method="?")
|
102
|
+
if response.nil?
|
103
|
+
log.error "Something went horribly wrong"
|
104
|
+
return
|
105
|
+
end
|
106
|
+
|
107
|
+
case ocsp_response.status
|
108
|
+
when OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL
|
109
|
+
serial_data = ocsp_response.basic.status.map do |status|
|
110
|
+
friendly_status = case status[1]
|
111
|
+
when 0
|
112
|
+
"VALID"
|
113
|
+
when 1
|
114
|
+
"REVOKED"
|
115
|
+
when 2
|
116
|
+
"UNKNOWN"
|
117
|
+
end
|
118
|
+
if ocsp_response.basic.status[0][0].respond_to?(:issuer_key_hash)
|
119
|
+
config_used = ocsp_signer.request_checker.configs_hash[ocsp_response.basic.status[0][0].issuer_key_hash]
|
120
|
+
else
|
121
|
+
config_used = ocsp_signer.request_checker.configs.find do |config|
|
122
|
+
#we need to create an OCSP::CertificateId object that has the right
|
123
|
+
#issuer so we can pass it to #cmp_issuer. This is annoying because
|
124
|
+
#CertificateId wants a cert and its issuer, but we don't want to
|
125
|
+
#force users to provide an end entity cert just to make this comparison
|
126
|
+
#work. So, we create a fake new cert and pass it in.
|
127
|
+
ee_cert = OpenSSL::X509::Certificate.new
|
128
|
+
ee_cert.issuer = config.ca_cert.cert.subject
|
129
|
+
issuer_certid = OpenSSL::OCSP::CertificateId.new(ee_cert,config.ca_cert.cert)
|
130
|
+
ocsp_response.basic.status[0][0].cmp_issuer(issuer_certid)
|
143
131
|
end
|
132
|
+
end
|
133
|
+
stats.record(config_used.ca_cert.subject.to_s, status[0].serial.to_s, friendly_status) if Dependo::Registry.has_key?(:stats)
|
134
|
+
status[0].serial.to_s+" Status: #{friendly_status}"
|
144
135
|
end
|
136
|
+
log.info { "#{method} Request For Serial(s): #{serial_data.join(",")} UserAgent: #{env["HTTP_USER_AGENT"]}" }
|
137
|
+
when OpenSSL::OCSP::RESPONSE_STATUS_UNAUTHORIZED
|
138
|
+
log.info { "#{method} Request For Unauthorized CA. UserAgent: #{env["HTTP_USER_AGENT"]}" }
|
139
|
+
when OpenSSL::OCSP::RESPONSE_STATUS_MALFORMEDREQUEST
|
140
|
+
log.info { "#{method} Malformed Request. UserAgent: #{env["HTTP_USER_AGENT"]}" }
|
141
|
+
end
|
142
|
+
end
|
145
143
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
end
|
160
|
-
|
161
|
-
response["Last-Modified"] = Time.now.httpdate
|
162
|
-
response["ETag"] = OpenSSL::Digest::SHA1.new(ocsp_response.to_der).to_s
|
163
|
-
response["Expires"] = ocsp_response.basic.status[0][5].httpdate
|
164
|
-
response["Cache-Control"] = "max-age=#{max_age.to_i}, public, no-transform, must-revalidate"
|
165
|
-
end
|
144
|
+
def build_headers(request_response)
|
145
|
+
ocsp_response = request_response[:response]
|
146
|
+
ocsp_request = request_response[:request]
|
147
|
+
|
148
|
+
# cache_headers is injected via config.ru
|
149
|
+
# we only cache if it's a RESPONSE_STATUS_SUCCESSFUL response and there's no nonce.
|
150
|
+
if cache_headers and not ocsp_response.basic.nil? and ocsp_response.check_nonce(ocsp_request) == R509::OCSP::Request::Nonce::BOTH_ABSENT
|
151
|
+
calculated_max_age = ocsp_response.basic.status[0][5] - Time.now
|
152
|
+
#same with max_cache_age
|
153
|
+
if not max_cache_age or ( max_cache_age > calculated_max_age )
|
154
|
+
max_age = calculated_max_age
|
155
|
+
else
|
156
|
+
max_age = max_cache_age
|
166
157
|
end
|
167
158
|
|
159
|
+
response["Last-Modified"] = Time.now.httpdate
|
160
|
+
response["ETag"] = OpenSSL::Digest::SHA1.new(ocsp_response.to_der).to_s
|
161
|
+
response["Expires"] = ocsp_response.basic.status[0][5].httpdate
|
162
|
+
response["Cache-Control"] = "max-age=#{max_age.to_i}, public, no-transform, must-revalidate"
|
163
|
+
end
|
168
164
|
end
|
165
|
+
|
166
|
+
end
|
169
167
|
end
|
data/lib/r509/ocsp/signer.rb
CHANGED
@@ -4,241 +4,241 @@ require 'r509/config'
|
|
4
4
|
require 'dependo'
|
5
5
|
|
6
6
|
# OCSP related classes (signing, response, request)
|
7
|
-
module R509::
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
7
|
+
module R509::OCSP
|
8
|
+
# A class for signing OCSP responses
|
9
|
+
class Signer
|
10
|
+
attr_reader :validity_checker,:request_checker
|
11
|
+
|
12
|
+
# @option options [Boolean] :copy_nonce copy nonce from request to response?
|
13
|
+
# @option options [R509::Config::CAConfigPool] :configs CAConfigPool object
|
14
|
+
# possible OCSP issuance roots that we want to issue OCSP responses for
|
15
|
+
def initialize(options)
|
16
|
+
if options.has_key?(:validity_checker)
|
17
|
+
@validity_checker = options[:validity_checker]
|
18
|
+
else
|
19
|
+
@validity_checker = R509::Validity::DefaultChecker.new
|
20
|
+
end
|
21
|
+
@request_checker = Helper::RequestChecker.new(options[:configs], @validity_checker)
|
22
|
+
@response_signer = Helper::ResponseSigner.new(options)
|
23
|
+
end
|
25
24
|
|
26
|
-
# @param request [String,OpenSSL::OCSP::Request] OCSP request (string or parsed object)
|
27
|
-
# @return [Hash]
|
28
|
-
# * :request [OpenSSL::OCSP::Request] parsed request object
|
29
|
-
# * :response [OpenSSL::OCSP::Response] full response object
|
30
|
-
def handle_request(request)
|
31
|
-
begin
|
32
|
-
parsed_request = OpenSSL::OCSP::Request.new request
|
33
|
-
rescue
|
34
|
-
return {:response => @response_signer.create_response(OpenSSL::OCSP::RESPONSE_STATUS_MALFORMEDREQUEST), :request => nil}
|
35
|
-
end
|
36
|
-
|
37
|
-
statuses = @request_checker.check_statuses(parsed_request)
|
38
|
-
if not @request_checker.validate_statuses(statuses)
|
39
|
-
return {:response => @response_signer.create_response(OpenSSL::OCSP::RESPONSE_STATUS_UNAUTHORIZED), :request => nil}
|
40
|
-
end
|
41
|
-
|
42
|
-
basic_response = @response_signer.create_basic_response(parsed_request,statuses)
|
43
|
-
|
44
|
-
{:response => @response_signer.create_response(
|
45
|
-
OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL,
|
46
|
-
basic_response
|
47
|
-
), :request => parsed_request}
|
48
|
-
end
|
49
25
|
|
26
|
+
# @param request [String,OpenSSL::OCSP::Request] OCSP request (string or parsed object)
|
27
|
+
# @return [Hash]
|
28
|
+
# * :request [OpenSSL::OCSP::Request] parsed request object
|
29
|
+
# * :response [OpenSSL::OCSP::Response] full response object
|
30
|
+
def handle_request(request)
|
31
|
+
begin
|
32
|
+
parsed_request = OpenSSL::OCSP::Request.new request
|
33
|
+
rescue
|
34
|
+
return {:response => @response_signer.create_response(OpenSSL::OCSP::RESPONSE_STATUS_MALFORMEDREQUEST), :request => nil}
|
35
|
+
end
|
36
|
+
|
37
|
+
statuses = @request_checker.check_statuses(parsed_request)
|
38
|
+
if not @request_checker.validate_statuses(statuses)
|
39
|
+
return {:response => @response_signer.create_response(OpenSSL::OCSP::RESPONSE_STATUS_UNAUTHORIZED), :request => nil}
|
40
|
+
end
|
41
|
+
|
42
|
+
basic_response = @response_signer.create_basic_response(parsed_request,statuses)
|
43
|
+
|
44
|
+
{:response => @response_signer.create_response(
|
45
|
+
OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL,
|
46
|
+
basic_response
|
47
|
+
), :request => parsed_request}
|
50
48
|
end
|
49
|
+
|
50
|
+
end
|
51
51
|
end
|
52
52
|
|
53
53
|
#Helper module for OCSP handling
|
54
|
-
module R509::
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
end
|
83
|
-
end
|
84
|
-
@validity_checker = validity_checker
|
85
|
-
if @validity_checker.nil?
|
86
|
-
raise R509::R509Error, "Must supply a R509::Validity::Checker"
|
87
|
-
end
|
88
|
-
if not @validity_checker.respond_to?(:check)
|
89
|
-
raise R509::R509Error, "The validity checker must have a check method"
|
90
|
-
end
|
54
|
+
module R509::OCSP::Helper
|
55
|
+
# checks requests for validity against a set of configs
|
56
|
+
class RequestChecker
|
57
|
+
include Dependo::Mixin
|
58
|
+
attr_reader :configs,:configs_hash
|
59
|
+
|
60
|
+
# @param [R509::Config::CAConfigPool] configs CAConfigPool object
|
61
|
+
# @param [R509::Validity::Checker] validity_checker an implementation of the R509::Validity::Checker class
|
62
|
+
def initialize(configs, validity_checker)
|
63
|
+
unless configs.kind_of?(R509::Config::CAConfigPool)
|
64
|
+
raise R509::R509Error, "Must pass R509::Config::CAConfigPool object"
|
65
|
+
end
|
66
|
+
if configs.all.empty?
|
67
|
+
raise R509::R509Error, "Must be at least one R509::Config object"
|
68
|
+
end
|
69
|
+
@configs = configs.all
|
70
|
+
test_cid = OpenSSL::OCSP::CertificateId.new(OpenSSL::X509::Certificate.new,OpenSSL::X509::Certificate.new)
|
71
|
+
if test_cid.respond_to?(:issuer_key_hash)
|
72
|
+
@configs_hash = {}
|
73
|
+
@configs.each do |config|
|
74
|
+
ee_cert = OpenSSL::X509::Certificate.new
|
75
|
+
ee_cert.issuer = config.ca_cert.cert.subject.name
|
76
|
+
# per RFC 5019
|
77
|
+
# Clients MUST use SHA1 as the hashing algorithm for the
|
78
|
+
# CertID.issuerNameHash and the CertID.issuerKeyHash values.
|
79
|
+
# so we can safely assume that our inbound hashes will be SHA1
|
80
|
+
issuer_certid = OpenSSL::OCSP::CertificateId.new(ee_cert,config.ca_cert.cert,OpenSSL::Digest::SHA1.new)
|
81
|
+
@configs_hash[issuer_certid.issuer_key_hash] = config
|
91
82
|
end
|
83
|
+
end
|
84
|
+
@validity_checker = validity_checker
|
85
|
+
if @validity_checker.nil?
|
86
|
+
raise R509::R509Error, "Must supply a R509::Validity::Checker"
|
87
|
+
end
|
88
|
+
if not @validity_checker.respond_to?(:check)
|
89
|
+
raise R509::R509Error, "The validity checker must have a check method"
|
90
|
+
end
|
91
|
+
end
|
92
92
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end
|
114
|
-
|
115
|
-
log.info "#{validated_config.ca_cert.subject.to_s} found for issuer" if validated_config
|
116
|
-
check_status(certid, validated_config)
|
117
|
-
}
|
93
|
+
# Loads and checks a raw OCSP request
|
94
|
+
#
|
95
|
+
# @param request [OpenSSL::OCSP::Request] OpenSSL OCSP Request object
|
96
|
+
# @return [Hash] hash from the check_status method
|
97
|
+
def check_statuses(request)
|
98
|
+
request.certid.map { |certid|
|
99
|
+
if certid.respond_to?(:issuer_key_hash)
|
100
|
+
validated_config = @configs_hash[certid.issuer_key_hash]
|
101
|
+
else
|
102
|
+
validated_config = @configs.find do |config|
|
103
|
+
#we need to create an OCSP::CertificateId object that has the right
|
104
|
+
#issuer so we can pass it to #cmp_issuer. This is annoying because
|
105
|
+
#CertificateId wants a cert and its issuer, but we don't want to
|
106
|
+
#force users to provide an end entity cert just to make this comparison
|
107
|
+
#work. So, we create a fake new cert and pass it in.
|
108
|
+
ee_cert = OpenSSL::X509::Certificate.new
|
109
|
+
ee_cert.issuer = config.ca_cert.cert.subject
|
110
|
+
issuer_certid = OpenSSL::OCSP::CertificateId.new(ee_cert,config.ca_cert.cert)
|
111
|
+
certid.cmp_issuer(issuer_certid)
|
112
|
+
end
|
118
113
|
end
|
119
114
|
|
120
|
-
#
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
# @param statuses [Array<Hash>] array of hashes from check_statuses
|
125
|
-
# @return [Boolean]
|
126
|
-
def validate_statuses(statuses)
|
127
|
-
validity = true
|
128
|
-
config = nil
|
129
|
-
|
130
|
-
statuses.each do |status|
|
131
|
-
if status[:config].nil?
|
132
|
-
validity = false
|
133
|
-
end
|
134
|
-
if config.nil?
|
135
|
-
config = status[:config]
|
136
|
-
end
|
137
|
-
if config != status[:config]
|
138
|
-
validity = false
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
validity
|
143
|
-
end
|
115
|
+
log.info "#{validated_config.ca_cert.subject.to_s} found for issuer" if validated_config
|
116
|
+
check_status(certid, validated_config)
|
117
|
+
}
|
118
|
+
end
|
144
119
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
return {
|
159
|
-
:certid => certid,
|
160
|
-
:status => validity_status.ocsp_status,
|
161
|
-
:revocation_reason => validity_status.revocation_reason,
|
162
|
-
:revocation_time => validity_status.revocation_time,
|
163
|
-
:config => validated_config
|
164
|
-
}
|
165
|
-
end
|
120
|
+
# Determines whether the statuses constitute a request that is compliant.
|
121
|
+
# No config means we don't know the CA, different configs means there are
|
122
|
+
# requests from two different CAs in there. Both are invalid.
|
123
|
+
#
|
124
|
+
# @param statuses [Array<Hash>] array of hashes from check_statuses
|
125
|
+
# @return [Boolean]
|
126
|
+
def validate_statuses(statuses)
|
127
|
+
validity = true
|
128
|
+
config = nil
|
129
|
+
|
130
|
+
statuses.each do |status|
|
131
|
+
if status[:config].nil?
|
132
|
+
validity = false
|
166
133
|
end
|
134
|
+
if config.nil?
|
135
|
+
config = status[:config]
|
136
|
+
end
|
137
|
+
if config != status[:config]
|
138
|
+
validity = false
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
validity
|
167
143
|
end
|
168
144
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
145
|
+
private
|
146
|
+
|
147
|
+
# Checks the status of a certificate with the corresponding CA
|
148
|
+
# @param certid [OpenSSL::OCSP::CertificateId] The certId object from check_statuses
|
149
|
+
# @param validated_config [R509::Config]
|
150
|
+
def check_status(certid, validated_config)
|
151
|
+
if(validated_config == nil) then
|
152
|
+
return {
|
153
|
+
:certid => certid,
|
154
|
+
:config => nil
|
155
|
+
}
|
156
|
+
else
|
157
|
+
validity_status = @validity_checker.check(validated_config.ca_cert.subject.to_s,certid.serial)
|
158
|
+
return {
|
159
|
+
:certid => certid,
|
160
|
+
:status => validity_status.ocsp_status,
|
161
|
+
:revocation_reason => validity_status.revocation_reason,
|
162
|
+
:revocation_time => validity_status.revocation_time,
|
163
|
+
:config => validated_config
|
164
|
+
}
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
#signs OCSP responses
|
170
|
+
class ResponseSigner
|
171
|
+
# @option options [Boolean] :copy_nonce
|
172
|
+
def initialize(options)
|
173
|
+
if options.has_key?(:copy_nonce)
|
174
|
+
@copy_nonce = options[:copy_nonce]
|
175
|
+
else
|
176
|
+
@copy_nonce = false
|
177
|
+
end
|
178
|
+
end
|
179
179
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
end
|
197
|
-
basic_response.add_status(status[:certid],
|
198
|
-
status[:status],
|
199
|
-
status[:revocation_reason],
|
200
|
-
revocation_time,
|
201
|
-
-1*status[:config].ocsp_start_skew_seconds,
|
202
|
-
status[:config].ocsp_validity_hours*3600,
|
203
|
-
[] #array of OpenSSL::X509::Extensions
|
204
|
-
)
|
205
|
-
end
|
206
|
-
|
207
|
-
#this method assumes the request data is validated by validate_request so all configs will be the same and
|
208
|
-
#we can choose to use the first one safely
|
209
|
-
config = statuses[0][:config]
|
210
|
-
|
211
|
-
#confusing, but R509::Cert contains R509::PrivateKey under #key. PrivateKey#key gives the OpenSSL object
|
212
|
-
#turns out BasicResponse#sign can take up to 4 params
|
213
|
-
#cert, key, array of OpenSSL::X509::Certificates, flags (not sure what the enumeration of those are)
|
214
|
-
basic_response.sign(config.ocsp_cert.cert,config.ocsp_cert.key.key,config.ocsp_chain)
|
180
|
+
# It is UNWISE to call this method directly because it assumes that the request is
|
181
|
+
# validated. You probably want to take a look at R509::OCSP::Signer#handle_request
|
182
|
+
#
|
183
|
+
# @param request [OpenSSL::OCSP::Request]
|
184
|
+
# @param statuses [Hash] hash from R509::OCSP::Helper::RequestChecker#check_statuses
|
185
|
+
# @return [OpenSSL::OCSP::BasicResponse]
|
186
|
+
def create_basic_response(request,statuses)
|
187
|
+
basic_response = OpenSSL::OCSP::BasicResponse.new
|
188
|
+
|
189
|
+
basic_response.copy_nonce(request) if @copy_nonce
|
190
|
+
|
191
|
+
statuses.each do |status|
|
192
|
+
#revocation time is retarded and is relative to now, so
|
193
|
+
#let's figure out what that is.
|
194
|
+
if status[:status] == OpenSSL::OCSP::V_CERTSTATUS_REVOKED
|
195
|
+
revocation_time = status[:revocation_time].to_i - Time.now.to_i
|
215
196
|
end
|
197
|
+
basic_response.add_status(status[:certid],
|
198
|
+
status[:status],
|
199
|
+
status[:revocation_reason],
|
200
|
+
revocation_time,
|
201
|
+
-1*status[:config].ocsp_start_skew_seconds,
|
202
|
+
status[:config].ocsp_validity_hours*3600,
|
203
|
+
[] #array of OpenSSL::X509::Extensions
|
204
|
+
)
|
205
|
+
end
|
206
|
+
|
207
|
+
#this method assumes the request data is validated by validate_request so all configs will be the same and
|
208
|
+
#we can choose to use the first one safely
|
209
|
+
config = statuses[0][:config]
|
210
|
+
|
211
|
+
#confusing, but R509::Cert contains R509::PrivateKey under #key. PrivateKey#key gives the OpenSSL object
|
212
|
+
#turns out BasicResponse#sign can take up to 4 params
|
213
|
+
#cert, key, array of OpenSSL::X509::Certificates, flags (not sure what the enumeration of those are)
|
214
|
+
basic_response.sign(config.ocsp_cert.cert,config.ocsp_cert.key.key,config.ocsp_chain)
|
215
|
+
end
|
216
216
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
end
|
217
|
+
# Builds final response.
|
218
|
+
#
|
219
|
+
# @param response_status [OpenSSL::OCSP::RESPONSE_STATUS_*] the primary response status
|
220
|
+
# @param basic_response [OpenSSL::OCSP::BasicResponse] an optional basic response object
|
221
|
+
# generated by create_basic_response
|
222
|
+
# @return [OpenSSL::OCSP::OCSPResponse]
|
223
|
+
def create_response(response_status,basic_response=nil)
|
224
|
+
|
225
|
+
# first arg is the response status code, comes from this list
|
226
|
+
# these can also be enumerated via OpenSSL::OCSP::RESPONSE_STATUS_*
|
227
|
+
#OCSPResponseStatus ::= ENUMERATED {
|
228
|
+
# successful (0), --Response has valid confirmations
|
229
|
+
# malformedRequest (1), --Illegal confirmation request
|
230
|
+
# internalError (2), --Internal error in issuer
|
231
|
+
# tryLater (3), --Try again later
|
232
|
+
# --(4) is not used
|
233
|
+
# sigRequired (5), --Must sign the request
|
234
|
+
# unauthorized (6) --Request unauthorized
|
235
|
+
#}
|
236
|
+
#
|
237
|
+
R509::OCSP::Response.new(
|
238
|
+
OpenSSL::OCSP::Response.create(
|
239
|
+
response_status, basic_response
|
240
|
+
)
|
241
|
+
)
|
243
242
|
end
|
243
|
+
end
|
244
244
|
end
|