How to programmatically check if a certificate has been revoked?

I am working on an automatic xcode build system. By doing some preliminary validation, I would like to check if the specified certificate file has been revoked. I understand that security verify-cert checks other cert properties, but not revokation. How can I check the review?

I am writing a build system in Ruby, but I am really open to ideas in any language.

I read this answer ( Openssl - How to check if a certificate has been revoked or not ), but the link at the bottom ( Does OpenSSL now automatically process certificate revocation lists (certificate revocation lists)? ) Gets into material that is used too often for my purposes (user uploading a revoked certificate has an extreme edge). Is there a simpler / ruby ​​verification method for recall?

Thanks in advance!

+4
source share
2 answers

Verifying certificate revocation can be a complex process. First you need to look for the CDP or OCIA AIA, then make a request, analyze the response and verify that the response is signed against a CA that is allowed to respond to this certificate. If it is a CRL, you need to find out if the serial number of the certificate you are checking is listed. If this is an OCSP, you need to find out if you received a “good” answer (as opposed to unknown, recalled, or any of the various OCSP responder errors, such as unauthorized ones). In addition, you can verify that the certificate is within its validity period and is tied to a trusted root. Finally, you must do revocation checks against each intermediate level, and also check the fingerprint of the certificate against explicit blacklists supported by Mozilla / Apple / Google / Microsoft.

I don’t know about any Ruby libraries that automate the review process for you (in the end I hope to add it to r509 ), but given your more specific use case here, there is some untested code that should point you in the right direction .

 require 'r509' require 'net/http' cert = R509::Cert.load_from_file("some_iphone_cert.pem") crl_uri = cert.crl_distribution_points.crl.uris[0] crl = Net::HTTP.get_response(URI(crl_uri)) # you may need to follow redirects here, but let assume you got the CRL. # Also note that the Apple WWDRCA CRL is like 28MB so you may want to cache this damned thing. OCSP would be nicer but it a bit trickier to validate. parsed_crl = R509::CRL::SignedList.new(crl) if not parsed_crl.verify(cert.public_key) raise StandardError, "Invalid CRL for certificate" end if parsed_crl.revoked?(cert.serial) puts 'revoked' end 

Unfortunately, due to the sheer size (~ 680 thousand records) of the Apple WWDRCA CRL recall, this check can be rather slow with the current r509 hash model.

If you are interested in going down the OCSP path, I can write how to generate OCSP queries / parse responses in Ruby.

Edit: iPhone developer certificates appear, I don’t have the built-in OCIA AIA, so the only revocation check option would be through the CRL distribution point, as shown above.

Edit2: Oh, why not, let him run an OCSP check in Ruby! For this we need a certificate and its certificate of issue. You cannot use the WWDRCA certificate for this, so just grab it from your favorite website. I use my own website.

 require 'net/http' require 'r509' cert = R509::Cert.load_from_file("my_website.pem") # get the first OCSP AIA URI. There can be more than one # (degenerate example!) ocsp_uri = cert.aia.ocsp.uris[0] issuer = R509::Cert.load_from_file("my_issuer.pem") cert_id = OpenSSL::OCSP::CertificateId.new(cert.cert,issuer.cert) request = OpenSSL::OCSP::Request.new request.add_certid(cert_id) # we're going to make a GET request per RFC 5019. You can also POST the # binary DER encoded version if you're more of an RFC 2560 partisan request_uri = URI(ocsp_uri+"/"+URI.encode_www_form_component(req_pem.strip) http_response = Net::HTTP.get_response(request_uri) if http_response.code != "200" raise StandardError, "Invalid response code from OCSP responder" end response = OpenSSL::OCSP::Response.new(http_response.body) if response.status != 0 raise StandardError, "Not a successful status" end if response.basic[0][0].serial != cert.serial raise StandardError, "Not the same serial" end if response.basic[0][1] != 0 # 0 is good, 1 is revoked, 2 is unknown. raise StandardError, "Not a good status" end current_time = Time.now if response.basic[0][4] > current_time or response.basic[0][5] < current_time raise StandardError, "The response is not within its validity window" end # we also need to verify that the OCSP response is signed by # a certificate that is allowed and chains up to a trusted root. # To do this you'll need to build an OpenSSL::X509::Store object # that contains the certificate you're checking + intermediates + root. store = OpenSSL::X509::Store.new store.add_cert(cert.cert) store.add_cert(issuer.cert) #assuming issuer is a trusted root here, but in reality you'll need at least one more certificate if response.basic.verify([],store) != true raise StandardError, "Certificate verification error" end 

In the above code example, the handling of many possible cases of edges is neglected, so it should only be considered as a starting point. Good luck

+10
source

Example Paul did not work with my local server, written by OpenSSL Cookbook, but worked with mail request

 # openssl ocsp -port 9080 -index db/index -rsigner root-ocsp.crt -rkey private/root-ocsp.key -CA root-ca.crt -text # openssl ocsp -issuer root-ca.crt -CAfile root-ca.crt -cert root-ocsp.crt -url http://127.0.0.1:9080 require 'net/http' require 'openssl' require 'base64' require 'test/unit' extend Test::Unit::Assertions def load_cert(name) OpenSSL::X509::Certificate.new(File.read(name)) end ca_file = issuer = load_cert('root-ca.crt') cert = load_cert('root-ocsp.crt') cid = OpenSSL::OCSP::CertificateId.new(cert, issuer) request = OpenSSL::OCSP::Request.new.add_certid(cid) # with get, invalid, server responding with # Invalid request # Responder Error: malformedrequest (1) # # encoded_der = Base64.encode64(request.to_der) # request_uri = URI.parse('http://127.0.0.1/' + URI.encode_www_form_component(encoded_der.strip)) # req = Net::HTTP::Get.new(request_uri.path, 'Content-Type' => 'application/ocsp-response') # http_resp = Net::HTTP.new(request_uri.host, '9080').request(req) # with post, work ocsp_uri = URI('http://127.0.0.1:9080/') http_resp = Net::HTTP.post(ocsp_uri, request.to_der, 'Content-Type' => 'application/ocsp-response') resp = OpenSSL::OCSP::Response.new(http_resp.body) assert_equal resp.status, OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL assert resp.basic.is_a? OpenSSL::OCSP::BasicResponse current_time = Time.now resp.basic.status.each do |status_arr| certificate_id, status, reason, revocation_time, this_update, next_update, extensions = status_arr assert_equal status, 0 # 0 is good, 1 is revoked, 2 is unknown. assert this_update < current_time assert next_update.nil? end first_cert_id = resp.basic.status[0][0] assert first_cert_id.cmp(cid) assert first_cert_id.cmp_issuer(cid) assert_equal first_cert_id.serial, cert.serial resp.basic.responses.each do |resp| assert resp.is_a? OpenSSL::OCSP::SingleResponse assert resp.check_validity end store = OpenSSL::X509::Store.new store.add_cert(cert) store.add_cert(issuer) # assuming issuer is a trusted root here, but in reality you'll need at least one more certificate assert resp.basic.verify([], store) 

PS At the moment, he is requesting the status of the ocsp certificate (for example, in a book), he wanted to request the status of the server / target, but first I need to try it using openssl cli, but here I have stumbled

Pss

did it thanks Steffen Ullrich

 # openssl ocsp -port 9080 -index db/index -rsigner subca-ocsp.crt -rkey private/subca-ocsp.key -CA sub-ca.crt -text # cat sub-ca.crt root-ca.crt > sub-and-root.crt # openssl ocsp -issuer sub-ca.crt -CAfile sub-and-root.crt -cert server.crt -url http://127.0.0.1:9080 require 'net/http' require 'openssl' require 'base64' require 'test/unit' extend Test::Unit::Assertions def load_cert(name) OpenSSL::X509::Certificate.new(File.read(name)) end subca = load_cert('sub-ca.crt') root = load_cert('root-ca.crt') cert = load_cert('server.crt') cid = OpenSSL::OCSP::CertificateId.new(cert, subca) request = OpenSSL::OCSP::Request.new.add_certid(cid) # with post, work ocsp_uri = URI('http://127.0.0.1:9080/') http_resp = Net::HTTP.post(ocsp_uri, request.to_der, 'Content-Type' => 'application/ocsp-response') resp = OpenSSL::OCSP::Response.new(http_resp.body) assert_equal resp.status, OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL assert resp.basic.is_a? OpenSSL::OCSP::BasicResponse first_cert_id = resp.basic.status[0][0] assert first_cert_id.cmp(cid) assert first_cert_id.cmp_issuer(cid) assert_equal first_cert_id.serial, cert.serial resp.basic.responses.each do |resp| assert resp.is_a? OpenSSL::OCSP::SingleResponse assert resp.check_validity end store = OpenSSL::X509::Store.new store.add_cert(cert) store.add_cert(subca) store.add_cert(root) assert resp.basic.verify([], store) 
0
source

All Articles