Skip to content

Instantly share code, notes, and snippets.

@drbrain
Created January 30, 2020 23:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save drbrain/e5b7480c98c0cd95d139460710ff652d to your computer and use it in GitHub Desktop.
Save drbrain/e5b7480c98c0cd95d139460710ff652d to your computer and use it in GitHub Desktop.
Ruby TLS tools
#!/usr/bin/env ruby
##
# Use this tool to extract X509 certificates from an HTTP server you want to
# connect to that is using a self-signed certificate. This is a
# Trust-on-First-Use operation so you are still at risk of various attacks
# including man-in-the-middle.
#
# This does allow you to uniformly use VERIFY_PEER across all HTTPS
# connections, as you now will be verifying the possibly-suspect connection
# using the self-signed certificate along with all others.
#
# To use this script first save the output of this script to "certs.pem".
#
# Then in your HTTP program load the "certs.pem":
#
# store = OpenSSL::X509::Store.new
# store.set_default_paths # optional, if you are connecting to servers
# # with and without self-signed certificates
# store.add_cert "/path/to/certs.pem"
#
# With Net::HTTP:
#
# http = Net::HTTP.new hostname, port
# http.use_ssl = true
# http.cert_store = store
# http.verify_mode = OpenSSL::SSL::VERIFY_PEER
# # … other setup
# http.start
# # … use connection
#
# With Net::HTTP::Persistent
#
# http = Net::HTTP::Persistent.new
# http.cert_store = store
# # … use connection
#
# With Mechanize:
#
# mechanize = Mechanize.new
# mechanize.cert_store = store
# # … use connection
require "openssl"
require "socket"
require "uri"
abort "usage: #$0 URI" if ARGV.empty?
uri = URI.parse ARGV.shift
abort "You must use an https URI" unless uri.scheme.downcase == "https"
certificates = []
context = OpenSSL::SSL::SSLContext.new
# This records the certificates presented by the server and marks them OK so
# we can see all the rest.
context.verify_callback = proc do |preverify_ok, store_context|
certificate = store_context.current_cert
certificates << certificate
true
end
socket = TCPSocket.new uri.hostname, uri.port
ssl_socket = OpenSSL::SSL::SSLSocket.new socket, context
# for Server name indication
ssl_socket.hostname = uri.hostname
# Connect and close to fetch certificates but do nothing else
ssl_socket.connect
ssl_socket.close
certificates.each do |certificate|
subject = certificate.subject
_, name, = subject.to_a.find { |name,|
name == "CN"
}
name ||= subject
alt_name = certificate.extensions.find { |extension|
extension.oid == "subjectAltName"
}
# Print canonical name and hostnames (if any) this certificate is supposed
# to work with, the subject alone if none are detected.
puts "# #{name}"
puts "# #{alt_name.value}" if alt_name
puts certificate.to_pem
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment