为何Ruby与gnutls-cli获取的证书信任链存在差异?
证书固定测试中gnutls-cli与Ruby获取证书链数量差异的原因分析
我在测试网站证书固定(cert pinning)时发现,gnutls-cli命令和Ruby代码获取的证书信任链数量有时不一致:
- 针对
github-cloud.s3.amazonaws.com,gnutls-cli返回4条证书,Ruby代码仅返回3条 - 针对
www.netflix.com,gnutls-cli返回2条证书,Ruby代码返回3条
测试结果示例
1. gnutls-cli 针对 github-cloud.s3.amazonaws.com 的输出
Certificate[0] info: subject `CN=*.s3.amazonaws.com' pin-sha256="hK1awhGE7onU0O+/0pwyTCX1ngEBhLhdNNtD8P11+xY=" Certificate[1] info: subject `CN=Amazon,OU=Server CA 1B,O=Amazon,C=US' pin-sha256="JSMzqOOrtyOT1kmau6zKhgT676hGgczD5VMdRMyJZFA=" Certificate[2] info: subject `CN=Amazon Root CA 1,O=Amazon,C=US' pin-sha256="++MBgDH5WGvL9Bcn5Be30cRcL0f5O+NyoXuWtQdX1aI=" Certificate[3] info: subject `CN=Starfield Services Root Certificate Authority - G2,O=Starfield Technologies' pin-sha256="KwccWaCgrnaw6tsrrSO61FgLacNgG2MMLq8GE6+oP5I="
2. Ruby代码针对 github-cloud.s3.amazonaws.com 的输出
/CN=*.s3.amazonaws.com hK1awhGE7onU0O+/0pwyTCX1ngEBhLhdNNtD8P11+xY= /C=US/O=Amazon/OU=Server CA 1B/CN=Amazon JSMzqOOrtyOT1kmau6zKhgT676hGgczD5VMdRMyJZFA= /C=US/O=Amazon/CN=Amazon Root CA 1 ++MBgDH5WGvL9Bcn5Be30cRcL0f5O+NyoXuWtQdX1aI=
3. gnutls-cli 针对 www.netflix.com 的输出
Certificate[0] info: subject `CN=www.netflix.com,O=Netflix\, Inc.' pin-sha256:3TGagkVvINvo827M04z0YZlg5kctebcod1Qwb83pA0s= Certificate[1] info: subject `CN=DigiCert TLS RSA SHA256 2020 CA1,O=DigiCert Inc' pin-sha256="RQeZkB42znUfsDIIFWIRiYEcKl7nHwNFwWCrnMMJbVc="
4. Ruby代码针对 www.netflix.com 的输出
/C=US/ST=California/L=Los Gatos/O=Netflix, Inc./CN=www.netflix.com 3TGagkVvINvo827M04z0YZlg5kctebcod1Qwb83pA0s= /C=US/O=DigiCert Inc/CN=DigiCert TLS RSA SHA256 2020 CA1 RQeZkB42znUfsDIIFWIRiYEcKl7nHwNFwWCrnMMJbVc= /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Global Root CA r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=
测试所用Ruby代码
#!/usr/bin/env ruby require 'colorize' require 'net/http' require 'openssl' require 'base64' domain = "www.netflix.com" http = Net::HTTP.new(domain, 443) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_PEER http.verify_callback = lambda do | preverify_ok, cert_store | return false unless preverify_ok end_cert = cert_store.chain[0] return true unless end_cert.to_der == cert_store.current_cert.to_der cert_store.chain.each do |i| sha256 = OpenSSL::Digest::SHA256.new digest = sha256.digest(i.public_key.to_der) spki = Base64.strict_encode64(digest) puts i.subject.to_s, spki end true end res = http.get '/'
差异原因解析
1. 证书链来源不同
gnutls-cli展示的是服务器主动发送的完整证书链,包含服务器证书、中间CA证书,甚至可能包含根证书(如果服务器配置了发送根证书)。比如s3的例子中,服务器额外发送了Starfield根证书,所以gnutls-cli输出4条。- Ruby依赖的OpenSSL库返回的是验证完成后的信任链:它会先接收服务器发送的证书,再结合本地信任存储补全缺失的根证书,同时过滤掉本地已信任的根证书(因为根证书是信任锚,不需要服务器传递)。
2. 本地信任存储的作用差异
- OpenSSL会自动从系统或指定的信任存储中查找缺失的根证书,完成信任链的构建。比如Netflix的例子,服务器只发送了服务器证书和中间CA证书,OpenSSL从本地信任存储中找到了DigiCert根证书,所以Ruby输出的链包含3条。
gnutls-cli默认不会从本地信任存储补充证书,只展示服务器发送的内容,所以Netflix的例子里只输出2条。
3. 根证书的处理逻辑
- 对于本地已信任的根证书:如果服务器发送了它,
gnutls-cli会直接展示;但OpenSSL会认为根证书是信任起点,不会将其加入返回的验证链中,所以s3的例子里Ruby输出少了Starfield根证书。 - 如果服务器没发送根证书,但OpenSSL能从本地找到,就会把它加入链中,导致Ruby输出的链比
gnutls-cli多一条(比如Netflix的例子)。
内容的提问来源于stack exchange,提问作者Jay
相关产品推荐
相关产品推荐

