#!/usr/bin/ruby require "socket" require "openssl" require "net/http" host = ARGV.shift address = ARGV.shift domains = ARGV context = OpenSSL::SSL::SSLContext.new context.verify_mode = OpenSSL::SSL::VERIFY_NONE begin socket = TCPSocket.new(address, 443) ssl = OpenSSL::SSL::SSLSocket.new(socket, context) ssl.sync_close = true ssl.hostname = domains.first ssl.connect rescue StandardError => e puts "Error connecting to #{host}: #{e.message}" end if ssl certificate = ssl.peer_cert chain = ssl.peer_cert_chain.drop(1) issuer = chain.first if Time.now < certificate.not_before puts "Certificate #{domains.first} on #{host} not valid until #{certificate.not_before}" elsif certificate.not_after - Time.now < 21 * 86400 puts "Certificate #{domains.first} on #{host} expires at #{certificate.not_after}" end digest = OpenSSL::Digest::SHA1.new certificate_id = OpenSSL::OCSP::CertificateId.new(certificate, issuer, digest) ocsp_request = OpenSSL::OCSP::Request.new.add_certid(certificate_id) authority_info_access = certificate.extensions.find { |ext| ext.oid == "authorityInfoAccess" } ocsp = authority_info_access.value.split("\n").find { |desc| desc.start_with?("OCSP") } ocsp_uri = URI(ocsp.sub(/^.* URI:/, "")) http_response = Net::HTTP.start(ocsp_uri.hostname, ocsp_uri.port) do |http| path = ocsp_uri.path path = "/" if path.empty? http.post(path, ocsp_request.to_der, "Content-Type" => "application/ocsp-request") end basic_response = OpenSSL::OCSP::Response.new(http_response.body).basic store = OpenSSL::X509::Store.new store.set_default_paths unless basic_response.verify(chain, store) raise "OCSP response is not signed by a trusted certificate" end single_response = basic_response.find_response(certificate_id) unless single_response raise "OCSP response does not have the status for the certificate" end unless single_response.check_validity raise "OCSP response is not valid" end if single_response.cert_status == OpenSSL::OCSP::V_CERTSTATUS_REVOKED puts "Certificate #{domains.first} on #{host} has been revoked" end subject_alt_name = certificate.extensions.find { |ext| ext.oid == "subjectAltName" } if subject_alt_name.nil? puts "Certificate #{domains.first} on #{host} has no subjectAltName" else alt_names = subject_alt_name.value.split(/\s*,\s*/).map { |n| n.sub(/^DNS:/, "") } domains.each do |domain| if alt_names.include?(domain) alt_names.delete(domain) else puts "Certificate #{domains.first} on #{host} is missing subjectAltName #{domain}" end end alt_names.each do |name| puts "Certificate #{domains.first} on #{host} has unexpected subjectAltName #{name}" end end ssl.close end