Class: Mongo::Socket::OcspVerifier Private
Do not use. This class is for internal use only.
| Relationships & Source Files | |
| Namespace Children | |
|
Classes:
| |
| Super Chains via Extension / Inclusion / Inheritance | |
|
Instance Chain:
self,
::Mongo::Loggable
|
|
| Inherits: | Object |
| Defined in: | lib/mongo/socket/ocsp_verifier.rb |
Overview
OCSP endpoint verifier.
After a TLS connection is established, this verifier inspects the certificate presented by the server, and if the certificate contains an OCSP URI, performs the OCSP status request to the specified ::Mongo::URI (following up to 5 redirects) to verify the certificate status.
Constant Summary
::Mongo::Loggable - Included
Class Method Summary
- .new(host_name, cert, ca_cert, cert_store, **opts) ⇒ OcspVerifier constructor Internal use only
Instance Attribute Summary
- #ca_cert readonly Internal use only
- #cert readonly Internal use only
- #cert_store readonly Internal use only
- #host_name readonly Internal use only
- #options readonly Internal use only
Instance Method Summary
- #cert_id Internal use only
- #ocsp_uris ⇒ Array<String> Internal use only
- #timeout Internal use only
- #verify ⇒ true | false Internal use only
- #verify_with_cache Internal use only
- #do_verify private Internal use only
- #handle_exceptions private Internal use only
- #raise_revoked_error(resp) private Internal use only
- #report_response_body(body) private Internal use only
- #report_uri(original_uri, uri) private Internal use only
- #return_ocsp_response(resp, errors = nil) private Internal use only
- #verify_one_responder(uri) private Internal use only
::Mongo::Loggable - Included
| #log_debug | Convenience method to log debug messages with the standard prefix. |
| #log_error | Convenience method to log error messages with the standard prefix. |
| #log_fatal | Convenience method to log fatal messages with the standard prefix. |
| #log_info | Convenience method to log info messages with the standard prefix. |
| #log_warn | Convenience method to log warn messages with the standard prefix. |
| #logger | Get the logger instance. |
| #_mongo_log_prefix, #format_message | |
Instance Attribute Details
#ca_cert (readonly)
# File 'lib/mongo/socket/ocsp_verifier.rb', line 72
attr_reader :host_name, :cert, :ca_cert, :cert_store, :
#cert (readonly)
# File 'lib/mongo/socket/ocsp_verifier.rb', line 72
attr_reader :host_name, :cert, :ca_cert, :cert_store, :
#cert_store (readonly)
#host_name (readonly)
# File 'lib/mongo/socket/ocsp_verifier.rb', line 72
attr_reader :host_name, :cert, :ca_cert, :cert_store, :
#options (readonly)
# File 'lib/mongo/socket/ocsp_verifier.rb', line 72
attr_reader :host_name, :cert, :ca_cert, :cert_store, :
Instance Method Details
#cert_id
#do_verify (private)
# File 'lib/mongo/socket/ocsp_verifier.rb', line 138
def do_verify # This synchronized array contains definitive pass/fail responses # obtained from the responders. We'll take the first one but due to # concurrency multiple responses may be produced and queued. @resp_queue = Queue.new # This synchronized array contains strings, one per responder, that # explain why each responder hasn't produced a definitive response. # These are concatenated and logged if none of the responders produced # a definitive respnose, or if the main thread times out waiting for # a definitive response (in which case some of the worker threads' # diagnostics may be logged and some may not). @resp_errors = Queue.new @req = OpenSSL::OCSP::Request.new @req.add_certid(cert_id) @req.add_nonce @serialized_req = @req.to_der @outstanding_requests = ocsp_uris.count @outstanding_requests_lock = Mutex.new threads = ocsp_uris.map do |uri| Thread.new do verify_one_responder(uri) end end resp = begin ::Timeout.timeout(timeout) do @resp_queue.shift end rescue ::Timeout::Error nil end threads.map(&:kill) threads.map(&:join) [ resp, @resp_errors ] end
#handle_exceptions (private)
# File 'lib/mongo/socket/ocsp_verifier.rb', line 303
def handle_exceptions yield rescue Error::ServerCertificateRevoked raise rescue StandardError => e Utils.warn_bg_exception( "Error performing OCSP verification for '#{host_name}'", e, ** ) false end
#ocsp_uris ⇒ Array<String>
# File 'lib/mongo/socket/ocsp_verifier.rb', line 79
def ocsp_uris @ocsp_uris ||= begin # https://tools.ietf.org/html/rfc3546#section-2.3 # prohibits multiple extensions with the same oid. ext = cert.extensions.detect do |ext| ext.oid == 'authorityInfoAccess' end if ext # Our test certificates have multiple OCSP URIs. ext.value.split("\n").select do |line| line.start_with?('OCSP - URI:') end.map do |line| line.split(':', 2).last end else [] end end end
#raise_revoked_error(resp) (private)
# File 'lib/mongo/socket/ocsp_verifier.rb', line 316
def raise_revoked_error(resp) redirect = if resp.uri == resp.original_uri '' else " (redirected from #{resp.original_uri})" end raise Error::ServerCertificateRevoked, "TLS certificate of '#{host_name}' has been revoked according to '#{resp.uri}'#{redirect} for reason '#{resp.revocation_reason}' at '#{resp.revocation_time}'" end
#report_response_body(body) (private)
# File 'lib/mongo/socket/ocsp_verifier.rb', line 334
def report_response_body(body) if body ": #{body}" else '' end end
#report_uri(original_uri, uri) (private)
#return_ocsp_response(resp, errors = nil) (private)
# File 'lib/mongo/socket/ocsp_verifier.rb', line 284
def return_ocsp_response(resp, errors = nil) if resp raise_revoked_error(resp) if resp.cert_status == OpenSSL::OCSP::V_CERTSTATUS_REVOKED true else reasons = [] errors.length.times do reasons << errors.shift end msg = if reasons.empty? "No responses from responders: #{ocsp_uris.join(', ')} within #{timeout} seconds" else "For responders #{ocsp_uris.join(', ')} with a timeout of #{timeout} seconds: #{reasons.join(', ')}" end log_warn("TLS certificate of '#{host_name}' could not be definitively verified via OCSP: #{msg}") false end end
#timeout
# File 'lib/mongo/socket/ocsp_verifier.rb', line 74
def timeout [:timeout] || 5 end
#verify ⇒ true | false
# File 'lib/mongo/socket/ocsp_verifier.rb', line 127
def verify handle_exceptions do return false if ocsp_uris.empty? resp, errors = do_verify return_ocsp_response(resp, errors) end end
#verify_one_responder(uri) (private)
# File 'lib/mongo/socket/ocsp_verifier.rb', line 180
def verify_one_responder(uri) original_uri = uri redirect_count = 0 http_response = nil loop do begin uri = URI(uri) http_response = Net::HTTP.start(uri.hostname, uri.port) do |http| path = uri.path path = '/' if path.empty? http.post(path, @serialized_req, 'content-type' => 'application/ocsp-request') end rescue IOError, SystemCallError => e @resp_errors << "OCSP request to #{report_uri(original_uri, uri)} failed: #{e.class}: #{e}" return false end code = http_response.code.to_i if (300..399).include?(code) redirected_uri = http_response.header['location'] uri = ::URI.join(uri, redirected_uri) redirect_count += 1 if redirect_count > 5 @resp_errors << "OCSP request to #{report_uri(original_uri, uri)} failed: too many redirects (6)" return false end next end if code >= 400 @resp_errors << ("OCSP request to #{report_uri(original_uri, uri)} failed with HTTP status code #{http_response.code}" + report_response_body(http_response.body)) return false end if code != 200 # There must be a body provided with the response, if one isn't # provided the response cannot be verified. @resp_errors << ("OCSP request to #{report_uri(original_uri, uri)} failed with unexpected HTTP status code #{http_response.code}" + report_response_body(http_response.body)) return false end break end resp = OpenSSL::OCSP::Response.new(http_response.body) unless resp.basic @resp_errors << "OCSP response from #{report_uri(original_uri, uri)} is #{resp.status}: #{resp.status_string}" return false end resp = resp.basic unless resp.verify([ ca_cert ], cert_store) # Ruby's OpenSSL binding discards error information - see # https://github.com/ruby/openssl/issues/395 @resp_errors << "OCSP response from #{report_uri(original_uri, uri)} failed signature verification; set `OpenSSL.debug = true` to see why" return false end if @req.check_nonce(resp) == 0 @resp_errors << "OCSP response from #{report_uri(original_uri, uri)} included invalid nonce" return false end single_response = resp.find_response(cert_id) unless single_response @resp_errors << "OCSP response from #{report_uri(original_uri, uri)} did not include information about the requested certificate" return false end resp = Response.new(single_response, uri, original_uri) unless resp.check_validity @resp_errors << "OCSP response from #{report_uri(original_uri, uri)} was invalid: this_update was in the future or next_update time has passed" return false end unless [ OpenSSL::OCSP::V_CERTSTATUS_GOOD, OpenSSL::OCSP::V_CERTSTATUS_REVOKED, ].include?(resp.cert_status) @resp_errors << "OCSP response from #{report_uri(original_uri, uri)} had a non-definitive status: #{resp.cert_status}" return false end # Note this returns the redirected URI @resp_queue << resp rescue StandardError => e Utils.warn_bg_exception("Error performing OCSP verification for '#{host_name}' via '#{uri}'", e, logger: [:logger], log_prefix: [:log_prefix], bg_error_backtrace: [:bg_error_backtrace]) false ensure @outstanding_requests_lock.synchronize do @outstanding_requests -= 1 @resp_queue << nil if @outstanding_requests == 0 end end
#verify_with_cache
# File 'lib/mongo/socket/ocsp_verifier.rb', line 108
def verify_with_cache handle_exceptions do return false if ocsp_uris.empty? resp = OcspCache.get(cert_id) return return_ocsp_response(resp) if resp resp, errors = do_verify OcspCache.set(cert_id, resp) if resp return_ocsp_response(resp, errors) end end