I found a strange phenomenon when using the command gnutls-cli
and ruby code to test the cert pinning of the website. Sometimes the number of certificate trust chains obtained by the two methods is different.
commandline gnutls-cli github-cloud.s3.amazonaws.com
will get 4:
(I removed some redundant information)
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="
Using 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=
commandline gnutls-cli www.netflix.com
will get 2:
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="
Using 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=
Here is Ruby Code:
#!/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 '/'
ruby code reference to Implementing HTTPS certificate/pubkey pinning with Ruby
Thanks!
Let's take www.netflix.com as an example. There are three certificates used in the trust chain:
CN=www.netflix.com
with the public key PIN 3TGagkVvINvo827M04z0YZlg5kctebcod1Qwb83pA0s=
, which is signed by:CN=DigiCert TLS RSA SHA256 2020 CA1
with the public key PIN RQeZkB42znUfsDIIFWIRiYEcKl7nHwNFwWCrnMMJbVc=
, which is signed by:CN=DigiCert Global Root CA
with the public key PIN r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=
#1 & #2 are sent to you by the remote host when you establish a connection to it. #3 exists on your device and is used to verify that #1 & #2 are valid certificates that you can trust.
When you use OpenSSL inside Net::HTTP to connect to these hosts it is being extra informative and printing the complete certificate chain of trust so that you know what Root CA signed #2. You weren't sent #3 over the wire along with #1 and #2 but it's telling you about it anyway because OpenSSL knows that it's part of the trust chain.
When you use gnutls-cli www.netflix.com --print-cert </dev/null 2>&1
to connect it is being succinct and printing only #1 and #2 -- the certificates that were sent by the remote host -- and instead telling you:
Status: The certificate is trusted.
... based on it knowing that the Root CA that you have on disk was used to sign the intermediary certificate and that the intermediary certificate was used to sign the Netflix certificate.
There's nothing different about the connections or the responses received; there's only a difference in what the tools print out when they are run.