【问题标题】:Implementing HTTPS certificate/pubkey pinning with Ruby使用 Ruby 实现 HTTPS 证书/公钥固定
【发布时间】:2014-04-01 07:55:22
【问题描述】:

我有自己的 HTTPS 服务,我正在通过另一个 Ruby 应用程序与之通信。我想在我的应用程序仓库中的一个已知时间点保存它的公钥证书,并将服务发送给我的公钥与存储的副本进行比较。要在外部服务器上安装证书,我可能必须将其转换为某种格式,因此服务器发送的文件不会相同。

我想对特定的公钥进行各种证书固定。我需要使用 OpenSSL 比较证书的哪些字段,以验证我从服务收到的 PK 是否与从服务器收到的相同?

我想 CN 和签名至少必须匹配。还有什么需要检查才能知道我拥有的公共证书与我收到的完全匹配(即相同的证书)?也许 OSSL 有一个内置的功能?

【问题讨论】:

  • 我认为最好固定公钥。在 X509 中,公钥与实体绑定,证书只是包装。此外,像谷歌这样的网站每 30 天左右轮换一次证书,同时重新验证相同的公钥。因此,如果您固定 Google 或 GMail,如果您固定证书,则每 30 天会收到警告/错误。
  • 我认为您可以通过验证签名,然后确保颁发者和序列号来做到这一点。 CA/Browser Baseline RequirementsRFC 5280RFC 6125 可能有有用的信息。

标签: ruby ssl https openssl certificate


【解决方案1】:

好的,在对 OpenSSL 进行了一番探讨之后,我已经了解了以下公钥固定的简单实现。其实很简单。不幸的是,我没有看到流行的 HTTP 中间件库(如 Faraday 和 HTTPClient)提供对 verify_callback 的访问权限,这实际上在每个 OpenSSL 会话中都可用。

在此示例中,如果 PK 与您之前固定的 PK 不匹配,会话将立即终止。请注意,不会使用 OpenSSL::SSL::VERIFY_NONE 调用该块(无论如何都不应该使用它)。

require 'net/http'
require 'openssl'

# Grab the cert received out of band by pigeon post
cert_code = File.read 'github.com.cer'
downloaded_cert = OpenSSL::X509::Certificate.new(cert_code)

# Tells us whether the private keys on the passed certificates match
# and use the same algo
def same_public_key?(ref_cert, actual_cert)
  pkr, pka = ref_cert.public_key, actual_cert.public_key

  # First check if the public keys use the same crypto...
  return false unless pkr.class == pka.class
  # ...and then - that they have the same contents
  return false unless pkr.to_pem == pka.to_pem

  true
end

# Configure a new HTTP object
http = Net::HTTP.new('github.com', 443)
http.use_ssl = true

# We will verify against our CAs in the root store, and with VERIFY_NONE
# the verify_callback will not fire at all, which defeats the purpose.
http.verify_mode = OpenSSL::SSL::VERIFY_PEER

# verify_callback will be called once for every certificate in the chain,
# starting with the top level certificate and ending with the actual certificate
# presented by the server we are contacting. Returning false from that callback
# will terminate the TLS session. Exceptions within the block will be suppressed.
#
# Citing the Ruby OpenSSL docs:
#
# A callback for additional certificate verification. The callback is invoked 
# for each certificate in the chain.
# 
# The callback is invoked with two values. preverify_ok indicates if the verification 
# was passed (true) or not (false). store_context is an OpenSSL::X509::StoreContext
# containing the context used for certificate verification.
# 
# If the callback returns false verification is stopped.
http.verify_callback = lambda do | preverify_ok, cert_store |
  return false unless preverify_ok

  # We only want to verify once, and fail the first time the callback
  # is invoked (as opposed to checking only the last time it's called).
  # Therefore we get at the whole authorization chain.
  # The end certificate is at the beginning of the chain (the certificate
  # for the host we are talking to)
  end_cert = cert_store.chain[0]

  # Only perform the checks if the current cert is the end certificate
  # in the chain. We can compare using the DER representation
  # (OpenSSL::X509::Certificate objects are not comparable, and for 
  # a good reason). If we don't we are going to perform the verification
  # many times - once per certificate in the chain of trust, which is wasteful
  return true unless end_cert.to_der == cert_store.current_cert.to_der

  # And verify the public key.
  same_public_key?(end_cert, downloaded_cert)
end

# This request will fail if the cert doesn't match
res = http.get '/'

如果您想进行整个证书固定并且证书不受轮换,您可以使用证书指纹:

def same_cert_fingerprint?(ref, actual)
  OpenSSL::Digest::SHA256.hexdigest(ref.to_der) ==  OpenSSL::Digest::SHA256.hexdigest(actual.to_der)
end

编辑:看起来至少 excon 最近实现了这个:

https://github.com/geemus/excon/commit/12437b79bad2a0e51bb4ac5b79c155eb88128245

【讨论】:

  • 对于我测试的内容,它没有验证证书的通用名称。我在 Heroku 中使用 2 台服务器进行了测试,即使每台服务器的证书都不同,请求在两台服务器中都成功。你能解释为什么会这样吗?我使用红宝石 2.6.6p146
  • Excon有办法验证主机名,不知道是不是这个问题:github.com/excon/excon/blob/…
  • 现在我看到证书的 CN 是 *.herokuapp.com,因此证书被认为是有效的,但这打开了使用具有不同主机名但具有不同主机名的证书进行 MITM 的大门同CN。这是证书的问题吗?
  • 据我所知,如果 CN 是通配符,证书适用于您的服务器的事实是有意的(也从 SSL 的角度来看)。因此,您可能需要自定义域和自定义证书才能从此类验证中受益。
【解决方案2】:

作为@Julik 回答的后续,RestClient (https://github.com/rest-client/rest-client) 自 1.6.8 起支持verify_callback

# The value of ssl_verify_callback is assigned to Net::HTTP#verify_callback.
RestClient::Resource.new(uri, ssl_verify_callback: ...).get

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-12-17
    • 2019-10-03
    • 1970-01-01
    • 2013-03-21
    • 1970-01-01
    • 2015-06-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多