module SSLTest::OCSP
Constants
- ERROR_CACHE_DURATION
Private Instance Methods
follow_ocsp_redirects(uri, data, open_timeout: 5, read_timeout: 5, redirection_limit: 5)
click to toggle source
Returns an array with [response, error_message]
# File lib/ssl-test/ocsp.rb, line 70 def follow_ocsp_redirects(uri, data, open_timeout: 5, read_timeout: 5, redirection_limit: 5) return [nil, "Too many redirections (> #{redirection_limit})"] if redirection_limit == 0 @logger&.debug { "SSLTest + OCSP: fetch URI #{uri}" } path = uri.path == "" ? "/" : uri.path http = Net::HTTP.new(uri.hostname, uri.port) http.open_timeout = open_timeout http.read_timeout = read_timeout http_response = http.post(path, data, "content-type" => "application/ocsp-request") case http_response when Net::HTTPSuccess @logger&.debug { "SSLTest + OCSP: 200 OK (#{http_response.body.bytesize} bytes)" } [http_response.body, nil] when Net::HTTPRedirection follow_ocsp_redirects(URI(http_response["location"]), data, open_timeout: open_timeout, read_timeout: read_timeout, redirection_limit: redirection_limit - 1) else @logger&.debug { "SSLTest + OCSP: Error: #{http_response.class}" } [nil, "Wrong response type (#{http_response.class})"] end end
ocsp_response_status_to_string(response_status)
click to toggle source
ruby-doc.org/stdlib-2.6.3/libdoc/openssl/rdoc/OpenSSL/OCSP.html#constants-list
# File lib/ssl-test/ocsp.rb, line 93 def ocsp_response_status_to_string(response_status) case response_status when OpenSSL::OCSP::RESPONSE_STATUS_INTERNALERROR "Internal error in issuer" when OpenSSL::OCSP::RESPONSE_STATUS_MALFORMEDREQUEST "Illegal confirmation request" when OpenSSL::OCSP::RESPONSE_STATUS_SIGREQUIRED "You must sign the request and resubmit" when OpenSSL::OCSP::RESPONSE_STATUS_TRYLATER "Try again later" when OpenSSL::OCSP::RESPONSE_STATUS_UNAUTHORIZED "Your request is unauthorized" else "Unknown reason" end end
ocsp_soft_fail_return(reason, unicity_key = nil)
click to toggle source
# File lib/ssl-test/ocsp.rb, line 136 def ocsp_soft_fail_return(reason, unicity_key = nil) error = [false, reason, nil] @ocsp_request_error_cache[unicity_key] = { error: error, expires: Time.now + ERROR_CACHE_DURATION } if unicity_key error end
revocation_reason_to_string(revocation_reason)
click to toggle source
# File lib/ssl-test/ocsp.rb, line 110 def revocation_reason_to_string(revocation_reason) # https://ruby-doc.org/stdlib-2.4.0/libdoc/openssl/rdoc/OpenSSL/OCSP.html#constants-list case revocation_reason when OpenSSL::OCSP::REVOKED_STATUS_AFFILIATIONCHANGED "The certificate subject's name or other information changed" when OpenSSL::OCSP::REVOKED_STATUS_CACOMPROMISE "This CA certificate was revoked due to a key compromise" when OpenSSL::OCSP::REVOKED_STATUS_CERTIFICATEHOLD "The certificate is on hold" when OpenSSL::OCSP::REVOKED_STATUS_CESSATIONOFOPERATION "The certificate is no longer needed" when OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE "The certificate was revoked due to a key compromise" when OpenSSL::OCSP::REVOKED_STATUS_NOSTATUS "The certificate was revoked for an unknown reason" when OpenSSL::OCSP::REVOKED_STATUS_REMOVEFROMCRL "The certificate was previously on hold and should now be removed from the CRL" when OpenSSL::OCSP::REVOKED_STATUS_SUPERSEDED "The certificate was superseded by a new certificate" when OpenSSL::OCSP::REVOKED_STATUS_UNSPECIFIED "The certificate was revoked for an unspecified reason" else "Unknown reason" end end
test_ocsp_revocation(cert, issuer:, chain:, **options)
click to toggle source
# File lib/ssl-test/ocsp.rb, line 7 def test_ocsp_revocation cert, issuer:, chain:, **options @ocsp_response_cache ||= {} @ocsp_request_error_cache ||= {} unicity_key = "#{cert.issuer}/#{cert.serial}" current_request_error_cache = @ocsp_request_error_cache[unicity_key] return current_request_error_cache[:error] if current_request_error_cache && Time.now <= current_request_error_cache[:expires] if @ocsp_response_cache[unicity_key].nil? || @ocsp_response_cache[unicity_key][:next_update] <= Time.now authority_info_access = cert.extensions.find do |extension| extension.oid == "authorityInfoAccess" end return ocsp_soft_fail_return("Missing authorityInfoAccess extension") unless authority_info_access # OpenSSL 2.2+ may simplify this: https://github.com/ruby/openssl/commit/ea702a106d3d8136c48f244593de95666be0edf9 ocsp = authority_info_access.value.split("\n").find do |description| description.start_with?("OCSP") end return ocsp_soft_fail_return("Missing OCSP URI in authorityInfoAccess extension") unless ocsp digest = OpenSSL::Digest::SHA1.new certificate_id = OpenSSL::OCSP::CertificateId.new(cert, issuer, digest) request = OpenSSL::OCSP::Request.new request.add_certid certificate_id request.add_nonce ocsp_uri = URI(ocsp[/URI:(.*)/, 1]) http_response, ocsp_request_error = follow_ocsp_redirects(ocsp_uri, request.to_der, **options) return ocsp_soft_fail_return("Request failed (URI: #{ocsp_uri}): #{ocsp_request_error}", unicity_key) unless http_response response = OpenSSL::OCSP::Response.new http_response # https://ruby-doc.org/stdlib-2.6.3/libdoc/openssl/rdoc/OpenSSL/OCSP.html#constants-list return ocsp_soft_fail_return("Unsuccessful response (URI: #{ocsp_uri}): #{ocsp_response_status_to_string(response.status)}", unicity_key) unless response.status == OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL basic_response = response.basic # Check the response signature store = OpenSSL::X509::Store.new store.set_default_paths # https://ruby-doc.org/stdlib-2.4.0/libdoc/openssl/rdoc/OpenSSL/OCSP/BasicResponse.html#method-i-verify return ocsp_soft_fail_return("Signature verification failed (URI: #{ocsp_uri})", unicity_key) unless basic_response.verify(chain, store) # https://ruby-doc.org/stdlib-2.4.0/libdoc/openssl/rdoc/OpenSSL/OCSP/Request.html#method-i-check_nonce return ocsp_soft_fail_return("Nonce check failed (URI: #{ocsp_uri})", unicity_key) unless request.check_nonce(basic_response) != 0 # https://ruby-doc.org/stdlib-2.3.0/libdoc/openssl/rdoc/OpenSSL/OCSP/BasicResponse.html#method-i-status response_certificate_id, status, reason, revocation_time, _this_update, next_update, _extensions = basic_response.status.first return ocsp_soft_fail_return("Serial check failed (URI: #{ocsp_uri})", unicity_key) unless response_certificate_id.serial == certificate_id.serial @ocsp_response_cache[unicity_key] = { status: status, reason: reason, revocation_time: revocation_time, next_update: next_update } end ocsp_response = @ocsp_response_cache[unicity_key] return [true, revocation_reason_to_string(ocsp_response[:reason]), ocsp_response[:revocation_time]] if ocsp_response[:status] == OpenSSL::OCSP::V_CERTSTATUS_REVOKED :ocsp_ok end