lib/r509/ocsp/responder/server.rb in r509-ocsp-responder-0.3.1 vs lib/r509/ocsp/responder/server.rb in r509-ocsp-responder-0.3.2

- old
+ new

@@ -1,169 +1,167 @@ -require 'rubygems' if RUBY_VERSION < "1.9" require 'sinatra/base' require 'r509' require 'r509/ocsp/signer' -require 'r509/validity/redis' require 'base64' require 'dependo' require 'logger' require 'time' require File.dirname(__FILE__)+'/ocsp-config.rb' # Capture USR2 calls so we can reload and print the config # I'd rather use HUP, but daemons like thin already capture that # so we can't use it. Signal.trap("USR2") do - R509::Ocsp::Responder::OcspConfig.load_config - R509::Ocsp::Responder::OcspConfig.print_config + R509::OCSP::Responder::OCSPConfig.load_config + R509::OCSP::Responder::OCSPConfig.print_config end -module R509::Ocsp::Responder - #error for status checking - class StatusError < StandardError - end +module R509::OCSP::Responder + #error for status checking + class StatusError < StandardError + end - class Server < Sinatra::Base - include Dependo::Mixin + class Server < Sinatra::Base + include Dependo::Mixin - configure do - mime_type :ocsp, 'application/ocsp-response' - disable :protection #disable Rack::Protection (for speed) - disable :logging - set :environment, :production - end + configure do + mime_type :ocsp, 'application/ocsp-response' + disable :protection #disable Rack::Protection (for speed) + disable :logging + set :environment, :production + end - error do - log.error env["sinatra.error"].inspect - log.error env["sinatra.error"].backtrace.join("\n") - "Something is amiss with our OCSP responder. You should ... wait?" - end + error do + log.error env["sinatra.error"].inspect + log.error env["sinatra.error"].backtrace.join("\n") + "Something is amiss with our OCSP responder. You should ... wait?" + end - error OpenSSL::OCSP::OCSPError do - "Invalid request" - end + error OpenSSL::OCSP::OCSPError do + "Invalid request" + end - error R509::Ocsp::Responder::StatusError do - "Down" - end + error R509::OCSP::Responder::StatusError do + "Down" + end - get '/favicon.ico' do - log.debug "go away. no children." - "go away. no children" - end + get '/favicon.ico' do + log.debug "go away. no children." + "go away. no children" + end - get '/status/?' do - begin - if Dependo::Registry[:ocsp_signer].validity_checker.is_available? - "OK" - else - raise R509::Ocsp::Responder::StatusError - end - rescue - raise R509::Ocsp::Responder::StatusError - end + get '/status/?' do + begin + if Dependo::Registry[:ocsp_signer].validity_checker.is_available? + "OK" + else + raise R509::OCSP::Responder::StatusError end + rescue + raise R509::OCSP::Responder::StatusError + end + end - get '/*' do - raw_request = params[:splat].join("/") - #remove any leading slashes (looking at you MS Crypto API) - raw_request.sub!(/^\/+/,"") - log.info { "GET Request: "+raw_request } - der = Base64.decode64(raw_request) - request_response = handle_ocsp_request(der, "GET") - build_headers(request_response) - request_response[:response].to_der - end + get '/*' do + raw_request = params[:splat].join("/") + #remove any leading slashes (looking at you MS Crypto API) + raw_request.sub!(/^\/+/,"") + log.info { "GET Request: "+raw_request } + der = Base64.decode64(raw_request) + request_response = handle_ocsp_request(der, "GET") + build_headers(request_response) + request_response[:response].to_der + end - post '/' do - if request.media_type == 'application/ocsp-request' - der = request.env["rack.input"].read - log.info { "POST Request: "+Base64.encode64(der).gsub!(/\n/,"") } - request_response = handle_ocsp_request(der, "POST") - request_response[:response].to_der - end - end + post '/' do + if request.media_type == 'application/ocsp-request' + der = request.env["rack.input"].read + log.info { "POST Request: "+Base64.encode64(der).gsub!(/\n/,"") } + request_response = handle_ocsp_request(der, "POST") + request_response[:response].to_der + end + end - private + private - def handle_ocsp_request(der, method) - begin - request_response = ocsp_signer.handle_request(der) + def handle_ocsp_request(der, method) + begin + request_response = ocsp_signer.handle_request(der) - log_ocsp_response(request_response[:response],method) + log_ocsp_response(request_response[:response],method) - content_type :ocsp - request_response - rescue StandardError => e - log.error "unexpected error #{e}" - raise e - end - end + content_type :ocsp + request_response + rescue StandardError => e + log.error "unexpected error #{e}" + raise e + end + end - def log_ocsp_response(ocsp_response, method="?") - if response.nil? - log.error "Something went horribly wrong" - return - end + def log_ocsp_response(ocsp_response, method="?") + if response.nil? + log.error "Something went horribly wrong" + return + end - case ocsp_response.status - when OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL - serial_data = ocsp_response.basic.status.map do |status| - friendly_status = case status[1] - when 0 - "VALID" - when 1 - "REVOKED" - when 2 - "UNKNOWN" - end - if ocsp_response.basic.status[0][0].respond_to?(:issuer_key_hash) - config_used = ocsp_signer.request_checker.configs_hash[ocsp_response.basic.status[0][0].issuer_key_hash] - else - config_used = ocsp_signer.request_checker.configs.find do |config| - #we need to create an OCSP::CertificateId object that has the right - #issuer so we can pass it to #cmp_issuer. This is annoying because - #CertificateId wants a cert and its issuer, but we don't want to - #force users to provide an end entity cert just to make this comparison - #work. So, we create a fake new cert and pass it in. - ee_cert = OpenSSL::X509::Certificate.new - ee_cert.issuer = config.ca_cert.cert.subject - issuer_certid = OpenSSL::OCSP::CertificateId.new(ee_cert,config.ca_cert.cert) - ocsp_response.basic.status[0][0].cmp_issuer(issuer_certid) - end - end - stats.record(config_used.ca_cert.subject.to_s, status[0].serial.to_s, friendly_status) if Dependo::Registry.has_key?(:stats) - status[0].serial.to_s+" Status: #{friendly_status}" - end - log.info { "#{method} Request For Serial(s): #{serial_data.join(",")} UserAgent: #{env["HTTP_USER_AGENT"]}" } - when OpenSSL::OCSP::RESPONSE_STATUS_UNAUTHORIZED - log.info { "#{method} Request For Unauthorized CA. UserAgent: #{env["HTTP_USER_AGENT"]}" } - when OpenSSL::OCSP::RESPONSE_STATUS_MALFORMEDREQUEST - log.info { "#{method} Malformed Request. UserAgent: #{env["HTTP_USER_AGENT"]}" } + case ocsp_response.status + when OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL + serial_data = ocsp_response.basic.status.map do |status| + friendly_status = case status[1] + when 0 + "VALID" + when 1 + "REVOKED" + when 2 + "UNKNOWN" + end + if ocsp_response.basic.status[0][0].respond_to?(:issuer_key_hash) + config_used = ocsp_signer.request_checker.configs_hash[ocsp_response.basic.status[0][0].issuer_key_hash] + else + config_used = ocsp_signer.request_checker.configs.find do |config| + #we need to create an OCSP::CertificateId object that has the right + #issuer so we can pass it to #cmp_issuer. This is annoying because + #CertificateId wants a cert and its issuer, but we don't want to + #force users to provide an end entity cert just to make this comparison + #work. So, we create a fake new cert and pass it in. + ee_cert = OpenSSL::X509::Certificate.new + ee_cert.issuer = config.ca_cert.cert.subject + issuer_certid = OpenSSL::OCSP::CertificateId.new(ee_cert,config.ca_cert.cert) + ocsp_response.basic.status[0][0].cmp_issuer(issuer_certid) end + end + stats.record(config_used.ca_cert.subject.to_s, status[0].serial.to_s, friendly_status) if Dependo::Registry.has_key?(:stats) + status[0].serial.to_s+" Status: #{friendly_status}" end + log.info { "#{method} Request For Serial(s): #{serial_data.join(",")} UserAgent: #{env["HTTP_USER_AGENT"]}" } + when OpenSSL::OCSP::RESPONSE_STATUS_UNAUTHORIZED + log.info { "#{method} Request For Unauthorized CA. UserAgent: #{env["HTTP_USER_AGENT"]}" } + when OpenSSL::OCSP::RESPONSE_STATUS_MALFORMEDREQUEST + log.info { "#{method} Malformed Request. UserAgent: #{env["HTTP_USER_AGENT"]}" } + end + end - def build_headers(request_response) - ocsp_response = request_response[:response] - ocsp_request = request_response[:request] + def build_headers(request_response) + ocsp_response = request_response[:response] + ocsp_request = request_response[:request] - # cache_headers is injected via config.ru - # we only cache if it's a RESPONSE_STATUS_SUCCESSFUL response and there's no nonce. - if cache_headers and not ocsp_response.basic.nil? and ocsp_response.check_nonce(ocsp_request) == R509::Ocsp::Request::Nonce::BOTH_ABSENT - calculated_max_age = ocsp_response.basic.status[0][5] - Time.now - #same with max_cache_age - if not max_cache_age or ( max_cache_age > calculated_max_age ) - max_age = calculated_max_age - else - max_age = max_cache_age - end - - response["Last-Modified"] = Time.now.httpdate - response["ETag"] = OpenSSL::Digest::SHA1.new(ocsp_response.to_der).to_s - response["Expires"] = ocsp_response.basic.status[0][5].httpdate - response["Cache-Control"] = "max-age=#{max_age.to_i}, public, no-transform, must-revalidate" - end + # cache_headers is injected via config.ru + # we only cache if it's a RESPONSE_STATUS_SUCCESSFUL response and there's no nonce. + if cache_headers and not ocsp_response.basic.nil? and ocsp_response.check_nonce(ocsp_request) == R509::OCSP::Request::Nonce::BOTH_ABSENT + calculated_max_age = ocsp_response.basic.status[0][5] - Time.now + #same with max_cache_age + if not max_cache_age or ( max_cache_age > calculated_max_age ) + max_age = calculated_max_age + else + max_age = max_cache_age end + response["Last-Modified"] = Time.now.httpdate + response["ETag"] = OpenSSL::Digest::SHA1.new(ocsp_response.to_der).to_s + response["Expires"] = ocsp_response.basic.status[0][5].httpdate + response["Cache-Control"] = "max-age=#{max_age.to_i}, public, no-transform, must-revalidate" + end end + + end end