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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16244084/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 03:17:17  来源:igfitidea点击:

How to programmatically check if a certificate has been revoked?

rubyxcodeopensslcertificatexcodebuild

提问by Alfie Hanssen

I'm working on an xcode automated build system. When performing some pre-build validation I would like to check if the specified certificate file has been revoked. I understand that security verify-certverifies other cert properties but not revokation. How can I check for revokation?

我正在研究 xcode 自动构建系统。在执行一些预构建验证时,我想检查指定的证书文件是否已被撤销。我知道security verify-cert验证其他证书属性而不是撤销。我如何检查吊销?

I'm writing the build system in Ruby but am really open to ideas in any language.

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

I read this answer (Openssl - How to check if a certificate is revoked or not) but the link towards the bottom (Does OpenSSL automatically handle CRLs (Certificate Revocation Lists) now?) gets into material that's a bit too involved for my purposes (a user uploading a revoked cert is a far out edge case). Is there a simpler / ruby oriented method for checking for revokation?

我阅读了这个答案(Openssl - 如何检查证书是否被撤销)但是底部的链接(OpenSSL 现在是否自动处理 CRL(证书撤销列表)?)上传吊销证书的用户是一个非常极端的情况)。是否有更简单的/面向 ruby​​ 的方法来检查撤销?

Thanks in advance!

提前致谢!

回答by Paul Kehrer

Checking if a certificate is revoked can be a complex process. First you have to look for a CDP or OCSP AIA, then make a request, parse the response, and check that the response is signed against by a CA that is authorized to respond for the certificate in question. If it is a CRL you then need to see if the serial number of the certificate you're checking is present in the list. If it is OCSP then you need to see if you've received a "good" response (as opposed to unknown, revoked, or any of the various OCSP responder errors like unauthorized). Additionally you may want to verify that the certificate is within its validity period and chains to a trusted root. Finally, you should do revocation checks against every intermediate as well and check the certificate's fingerprint against the explicit blacklists that Mozilla/Apple/Google/Microsoft maintain.

检查证书是否被吊销可能是一个复杂的过程。首先,您必须查找 CDP 或 OCSP AIA,然后发出请求,解析响应,并检查响应是否由有权响应相关证书的 CA 签名。如果它是 CRL,那么您需要查看您正在检查的证书的序列号是否存在于列表中。如果是 OCSP,那么您需要查看是否收到了“良好”响应(而不是未知、已撤销或任何各种 OCSP 响应者错误,例如未经授权)。此外,您可能想要验证证书是否在其有效期内并链接到受信任的根。最后,您还应该对每个中间件进行吊销检查并检查证书'

I'm unaware of any Ruby libraries that automate the revocation checking process for you (eventually I hope to add it to r509), but given your more specific use case here's some untested code that should point you in the right direction.

我不知道有任何 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

Unfortunately, due to the enormous size (~680k entries) of the Apple WWDRCA CRL this check can be quite slow with r509's current hash map model.

不幸的是,由于 Apple WWDRCA CRL 的巨大尺寸(约 680k 个条目),使用 r509 的当前哈希映射模型,此检查可能会非常慢。

If you're interested in going down the OCSP path I can write up how to generate OCSP requests/parse responses in Ruby as well.

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

Edit: It appears the iPhone developer certificates I have do not contain an embedded OCSP AIA so the only option for revocation checking will be via CRL distribution point as presented above.

编辑:看来我拥有的 iPhone 开发人员证书不包含嵌入式 OCSP AIA,因此吊销检查的唯一选项将是通过上述 CRL 分发点。

Edit2: Oh why not, let's do an OCSP check in Ruby! For this we'll need the certificate and its issuing certificate. You can't use a WWDRCA certificate for this so just grab one from your favorite website. I'm using my own website.

Edit2:哦,为什么不,让我们在 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

The example code above neglects to handle many possible edge cases, so it should be considered a starting point only. Good luck!

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

回答by srghma

Paul's example has not worked with my local server, made by OpenSSL Cookbook, but have worked with post request

保罗的例子没有在我的本地服务器上工作,由 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)

P.S. For now it requesting status of ocsp certificate (like in book), wanted to request server/end-entity status, but at first I have to try it with openssl cli, and here I have stumbled

PS 现在它请求 ocsp 证书的状态(如书中),想请求服务器/终端实体状态,但起初我必须用 openssl cli 尝试它,在这里我偶然发现

P.S.S

PSS

done this, thanks Steffen Ullrich

完成了这个,感谢 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)