导读:本期聚焦于BIT程序员创作的《如何在Ruby OpenSSL中为X509证书配置SAN、CRL分发点与OCSP装订?》,敬请观看详情。生成一张能在现代浏览器中同时通过主机名校验和吊销状态检查的TLS证书,仅填写CN已经不可行。SAN扩展负责声明服务可用的DNS名称与IP地址,CRL分发点和OCSP AIA扩展则把吊销信息查询地址提供给客户端。本文以Ruby OpenSSL库为核心,演示如何用ExtensionFactory构造subjectAltName、crlDistributionPoints和authorityInfoAccess扩展,并深入介绍OCSP装订的工作流程。文章会给出可直接运行的证书生成代码,包括多域名SAN配置、CRL URI写入、OCSP响应者地址声明,以及使用OpenSSL::OCSP构造请求、解析基本响应、验证签名链和读取证书状态。读完可以掌握在Ruby服务端证书中正确声明这些关键扩展的方法,也能理解OCSP装订如何降低客户端在线查询OCSP响应者带来的延迟和隐私风险。

现代浏览器对TLS证书的校验逻辑已经发生明显变化,仅依赖CN字段识别服务主机名的方式基本退出历史舞台。SAN、CRL分发点以及OCSP响应者地址这三个扩展,分别承担了主机名匹配、证书吊销检查和在线状态查询的职责。Ruby的OpenSSL标准库把X509证书扩展的生成和OCSP协议操作封装得比较完整,下面从零开始构造一张带这些关键扩展的证书,并说明如何处理OCSP装订数据。

如何在Ruby OpenSSL中为X509证书配置SAN、CRL分发点与OCSP装订?

一、使用ExtensionFactory生成带SAN的证书

SAN全称Subject Alternative Name,中文一般称为主题备用名称。这个扩展用来声明证书可用于哪些DNS名称、IP地址、URI或者邮箱地址。如果证书面向的是ipipp.com和www.ipipp.com两个域名,同时还希望覆盖本机测试用的127.0.0.1,就需要把这三个值都写入subjectAltName扩展。浏览器校验TLS握手时,会用用户实际访问的主机名与SAN列表做匹配,而不是去读取subject里的CN字段。

Ruby生成X509证书时,不能直接通过属性赋值添加扩展,需要借助OpenSSL::X509::ExtensionFactory。这个工厂对象负责把人类可读的扩展配置转换成OpenSSL能够识别的ASN.1结构。创建ExtensionFactory之后,必须把subject_certificate和issuer_certificate指向正在生成的证书对象,工厂才能正确处理某些依赖上下文信息的扩展。比如subjectAltName虽然不依赖签发者,但保持赋值习惯可以避免后续添加basicConstraints或keyUsage时出现空指针错误。

下面代码先创建RSA密钥和自签名证书,然后通过ExtensionFactory添加SAN扩展。cert.version必须设置为2,因为X509v3扩展只存在于版本3证书中,版本号从0开始计数,所以代码里写2。关键扩展basicConstraints标记为CA:FALSE,表示这不是CA证书;keyUsage限制为数字签名和密钥加密;extendedKeyUsage声明为serverAuth,说明证书用于TLS服务器身份验证。

require 'openssl'

key = OpenSSL::PKey::RSA.new(2048)

name = OpenSSL::X509::Name.parse('/C=CN/ST=Beijing/O=Example Corp/CN=ipipp.com')
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = 2
cert.subject = name
cert.issuer = name
cert.public_key = key.public_key
cert.not_before = Time.now
cert.not_after = Time.now + 365 * 24 * 60 * 60

ef = OpenSSL::X509::ExtensionFactory.new
ef.subject_certificate = cert
ef.issuer_certificate = cert

san_ext = ef.create_extension(
  'subjectAltName',
  'DNS:ipipp.com,DNS:www.ipipp.com,IP:127.0.0.1'
)
cert.add_extension(san_ext)

cert.add_extension(ef.create_extension('basicConstraints', 'CA:FALSE', true))
cert.add_extension(ef.create_extension('keyUsage', 'digitalSignature,keyEncipherment', true))
cert.add_extension(ef.create_extension('extendedKeyUsage', 'serverAuth', false))

cert.sign(key, OpenSSL::Digest::SHA256.new)
File.write('server_cert.pem', cert.to_pem)
File.write('server_key.pem', key.to_pem)

puts cert.to_text

运行这段代码后可以打开server_cert.pem查看证书详情,或者直接观察命令行输出中的X509v3 Subject Alternative Name区域。SAN值中的DNS条目使用DNS:前缀,IP地址使用IP:前缀,多个条目用英文逗号隔开。如果后续需要给同一张证书追加额外的SAN配置,不能简单修改扩展然后重签,因为证书内容变化后签名也会失效,必须重新生成并签名。

二、写入CRL分发点扩展

CRL是证书吊销列表,由CA定期签发,列出了已经被吊销但尚未到期的证书序列号。CRL分发点扩展为客户端提供URL地址,客户端可以下载该列表并检查当前证书是否被吊销。与OCSP相比,CRL文件通常较大,而且存在更新延迟,但它的优势在于可离线缓存,不要求每次校验都实时查询CA服务器。

在Ruby里写入CRL分发点同样使用ExtensionFactory。扩展名称是crlDistributionPoints,值字符串格式为URI:加CRL文件的地址。如果需要声明多个分发点,可以用逗号分隔多个URI条目。创建扩展后调用cert.add_extension即可。这个扩展没有critical标记的要求,大部分证书会把它设置为非关键扩展,不过即便客户端忽略CRL信息,TLS握手依然可以继续。

crl_dp_ext = ef.create_extension(
  'crlDistributionPoints',
  'URI:http://crl.ipipp.com/root.crl'
)
cert.add_extension(crl_dp_ext)

实际生产环境中,CRL分发点地址必须是CA真实发布的地址,并且需要保证服务端能够稳定访问。如果地址错误或无法访问,客户端只是无法完成吊销检查,不会直接导致证书校验失败,除非客户端策略要求强制吊销检查。由于CRL下载延迟较高,很多公网服务倾向采用OCSP作为主要吊销查询方式,同时保留CRL作为后备方案。

三、配置OCSP AIA扩展与OCSP装订

OCSP是一种在线证书状态查询协议,客户端把证书序列号和CA证书摘要发给OCSP响应者,响应者返回好、已吊销或未知三种状态。与CRL相比,OCSP响应体积小、实时性更强,但每个TLS客户端都去查询OCSP响应者会带来额外延迟,还会泄露用户正在访问哪个站点。OCSP装订就是为了解决这个问题:TLS服务器提前从响应者获取OCSP响应,并在握手期间将响应附加发送给客户端,这样客户端不再单独向外发起查询。

证书中的OCSP响应者地址写在authorityInfoAccess扩展里,这是一种AIA扩展类型。Ruby中创建该扩展时,值字符串格式为OCSP;URI:http://ocsp.ipipp.com/。分号前面的OCSP表示访问方法,URI后缀给出响应者地址。如果还要声明CA签发者证书下载地址,可以继续追加caIssuers;URI:http://ca.ipipp.com/ca.crt,用逗号分隔不同条目。代码片段如下:

ocsp_aia_ext = ef.create_extension(
  'authorityInfoAccess',
  'OCSP;URI:http://ocsp.ipipp.com/'
)
cert.add_extension(ocsp_aia_ext)

仅仅把OCSP地址写入证书并不会自动生成装订数据,装订需要TLS服务器在握手前主动请求OCSP响应。Ruby的OpenSSL库提供了OpenSSL::OCSP模块,可以构造请求、发送给OCSP响应者并解析返回结果。下面代码演示如何根据叶子证书和签发者证书创建OCSP请求,使用Net::HTTP发送给响应者,然后验证基本响应的签名链,并读取证书状态。

require 'net/http'

issuer_cert = OpenSSL::X509::Certificate.new(File.read('issuer_cert.pem'))
leaf_cert = OpenSSL::X509::Certificate.new(File.read('server_cert.pem'))

cid = OpenSSL::OCSP::CertificateId.new(leaf_cert, issuer_cert)
req = OpenSSL::OCSP::Request.new
req.add_certid(cid)

uri = URI('http://ocsp.ipipp.com/')
http = Net::HTTP.new(uri.host, uri.port)
response = http.post(uri.path, req.to_der, 'content-type' => 'application/ocsp-request')

ocsp_resp = OpenSSL::OCSP::Response.new(response.body)
basic = ocsp_resp.basic

store = OpenSSL::X509::Store.new
store.add_cert(issuer_cert)

unless basic.verify([], store)
  raise 'OCSP basic response signature verification failed'
end

status = basic.status.first
status_code = status[1]

case status_code
when 0
  puts 'certificate is good'
when 1
  puts 'certificate is revoked'
else
  puts 'certificate status unknown'
end

stapled_data = basic.to_der
File.binwrite('ocsp_staple.der', stapled_data)

上面代码中basic是OpenSSL::OCSP::BasicResponse对象,它包含实际的证书状态信息。basic.verify用于验证响应是否由受信任的证书签署,如果OCSP响应由颁发该证书的CA直接签名,把CA证书加入store即可;如果响应者使用单独的OCSP签名证书,需要把响应者证书也加入验证链。staple数据最终以DER格式保存,TLS服务器需要在握手时把这段数据作为TLS扩展发送。Ruby标准库的SSLContext在服务端直接设置OCSP装订数据的能力有限,通常要在更底层的OpenSSL回调中完成,或者把生成的DER数据交给Nginx、Apache等服务器配置使用。

四、读取和检查证书中的扩展

生成证书后,常常需要验证扩展是否正确写入。Ruby中证书对象的extensions方法会返回一个扩展对象数组,每个对象都有oid和value方法。可以遍历数组,根据OID找到subjectAltName、crlDistributionPoints和authorityInfoAccess。OID分别为2.5.29.17、2.5.29.31和1.3.6.1.5.5.7.1.1,但直接用名称匹配更方便。扩展对象的value是OpenSSL格式化后的字符串,通常可以直接打印确认。

cert.extensions.each do |ext|
  case ext.oid
  when 'subjectAltName'
    puts "SAN: #{ext.value}"
  when 'crlDistributionPoints'
    puts "CRL DP: #{ext.value}"
  when 'authorityInfoAccess'
    puts "AIA: #{ext.value}"
  end
end

排查扩展相关问题时,最常见的一个错误是ExtensionFactory没有正确设置subject_certificate或issuer_certificate,导致创建扩展时抛出异常。另一个常见问题是值字符串的格式写错,比如把OCSP AIA写成了URI:OCSP;http://...或者把SAN的DNS前缀写成了DNS=ipipp.com。扩展值必须严格遵循OpenSSL期望的ASN.1字符串表示,否则创建时会报错,或者生成的扩展在客户端看来无法解析。如果需要在证书中同时声明多个SAN类型,DNS、IP、email条目可以混合出现,但每个条目都要带正确前缀。

整体来看,SAN、CRL分发点和OCSP装订三者构成了一张现代TLS服务证书的核心扩展集合。SAN解决主机名验证问题,CRL分发点提供离线吊销检查能力,OCSP AIA加装订则实现高效、隐私友好的实时状态查询。用Ruby完成这些操作并不复杂,关键是理解每一步生成的扩展在TLS生态中的作用,以及如何把OCSP请求与验证流程串起来。

Ruby OpenSSLX509证书扩展OCSP装订修改时间:2026-10-02 01:48:02

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/1002/64469.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。