使用Ruby实现HTTPS证书/ pubkey固定 [英] Implementing HTTPS certificate/pubkey pinning with Ruby

查看:175
本文介绍了使用Ruby实现HTTPS证书/ pubkey固定的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have my own HTTPS service that I am talking to from another Ruby app. I would like to save it's public key certificate at a known point in time in my app repo, and compare the public key that the service sends me with the stored copy. To install the certificate on the external server I'm likely to have to convert it to some format, so the file sent by the server is not going to be the same.

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

I want to do cert pinning of sorts, to that specific public key. What are the fields of the cert I need to compare using OpenSSL to verify that the PK I received from the service is the same as the one that's received from the server?

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

I imagine the CN and the signature have to match at least. What else needs to be checked to know that the public cert I have matches the one I've received exactly (i.e. is the same cert)? Maybe OSSL has a built-in facility for this?

推荐答案

好的,在OpenSSL上做了一些戳之后我已经到了以下简单实现公钥锁定。它实际上非常简单。不幸的是,我没有看到流行的HTTP中间件库(如Faraday和HTTPClient)访问 verify_callback ,这实际上可用于每个OpenSSL会话。

Ok, after a bit of poking at OpenSSL I've arrived at the following simple implementation of public key pinning. It's actually very simple. Unfortunately I don't see popular HTTP middleware libraries (like Faraday and HTTPClient) giving access to the verify_callback which is actually available on every OpenSSL session.

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

In this example, the session will be terminated immediately if the PK doesn't match the one you have pinned previously. Note that the block will not be called with OpenSSL::SSL::VERIFY_NONE (which should never never never ever be used anyway).

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

如果你想做整个证书固定和证书不受轮换,你可以使用证书指纹:

If you want to do whole certificate pinning and certs are not subject to rotation, you can use cert fingerprints:

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

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

looks like at least excon implemented this recently:

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

这篇关于使用Ruby实现HTTPS证书/ pubkey固定的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆