如何以编程方式检查证书是否已被撤销?

我正在开发一个xcode自动构建系统。 执行一些预构建validation时,我想检查指定的证书文件是否已被撤销。 我了解security verify-certvalidation其他证书属性,但不validationrevokation。 我怎样才能检查是否有效?

我正在用Ruby编写构建系统,但我对任何语言的思想都很开放。

我读了这个答案( Openssl – 如何检查证书是否被撤销 )但是底部的链接( OpenSSL现在是否自动处理CRL(证书撤销列表)? )进入的材料对我来说有点过于牵连(上传撤销证书的用户是远远不够的情况)。 是否有更简单/ ruby​​导向的方法来检查revokation?

提前致谢!

检查证书是否被撤销可能是一个复杂的过程。 首先,您必须查找CDP或OCSP AIA,然后发出请求,解析响应,并检查响应是否由有权响应相关证书的CA签署。 如果是CRL,则需要查看列表中是否存在您正在检查的证书的序列号。 如果它是OCSP,那么你需要看看你是否收到了“好的”响应(而不是未知,撤销或任何OCSP响应者错误,如未经授权)。 此外,您可能需要validation证书是否在其有效期内并链接到受信任的根。 最后,您还应对每个中间人进行撤销检查,并根据Mozilla / Apple / Google / Microsoft维护的明确黑名单检查证书的指纹。

我没有意识到任何Ruby库为你自动化撤销检查过程(最终我希望将它添加到r509 ),但是考虑到你更具体的用例,这里有一些未经测试的代码应该指向正确的方向。

 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's 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's 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 

不幸的是,由于Apple WWDRCA CRL的大小(~680k条目),使用r509的当前哈希映射模型,这种检查可能会非常慢。

如果您对沿着OCSP路径感兴趣,我可以编写如何在Ruby中生成OCSP请求/解析响应。

编辑:看来iPhone开发人员证书我没有包含嵌入式OCSP AIA,因此撤销检查的唯一选择是通过CRL分发点,如上所示。

编辑2:哦,为什么不呢,让我们在Ruby中进行OCSP检查! 为此,我们需要证书及其颁发证书。 您不能使用WWDRCA证书,因此只需从您喜欢的网站上获取一个。 我正在使用自己的网站。

 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 

上面的示例代码忽略了处理许多可能的边缘情况,因此它应该仅被视为起点。 祝好运!

保罗的例子没有使用我的本地服务器,由OpenSSL Cookbook制作,但是已经使用了post请求

 # 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现在它请求ocsp证书的状态(如书中),想要请求服务器/最终实体状态,但首先我必须尝试使用​​openssl cli, 在这里我偶然发现

PSS

做到这一点,谢谢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)