11 context = OpenSSL::SSL::SSLContext.new
12 context.verify_mode = OpenSSL::SSL::VERIFY_NONE
15 socket = TCPSocket.new(address, 443)
17 ssl = OpenSSL::SSL::SSLSocket.new(socket, context)
19 ssl.hostname = domains.first
21 rescue StandardError => e
22 puts "Error connecting to #{host}: #{e.message}"
26 certificate = ssl.peer_cert
27 chain = ssl.peer_cert_chain.drop(1)
30 if Time.now < certificate.not_before
31 puts "Certificate #{domains.first} on #{host} not valid until #{certificate.not_before}"
32 elsif certificate.not_after - Time.now < 21 * 86400
33 puts "Certificate #{domains.first} on #{host} expires at #{certificate.not_after}"
36 digest = OpenSSL::Digest::SHA1.new
37 certificate_id = OpenSSL::OCSP::CertificateId.new(certificate, issuer, digest)
38 ocsp_request = OpenSSL::OCSP::Request.new.add_certid(certificate_id)
40 authority_info_access = certificate.extensions.find { |ext| ext.oid == "authorityInfoAccess" }
41 ocsp = authority_info_access.value.split("\n").find { |desc| desc.start_with?("OCSP") }
42 ocsp_uri = URI(ocsp.sub(/^.* URI:/, ""))
44 http_response = Net::HTTP.start(ocsp_uri.hostname, ocsp_uri.port) do |http|
46 path = "/" if path.empty?
47 http.post(path, ocsp_request.to_der, "Content-Type" => "application/ocsp-request")
50 basic_response = OpenSSL::OCSP::Response.new(http_response.body).basic
52 store = OpenSSL::X509::Store.new
53 store.set_default_paths
55 unless basic_response.verify(chain, store)
56 raise "OCSP response is not signed by a trusted certificate"
59 single_response = basic_response.find_response(certificate_id)
61 unless single_response
62 raise "OCSP response does not have the status for the certificate"
65 unless single_response.check_validity
66 raise "OCSP response is not valid"
69 if single_response.cert_status == OpenSSL::OCSP::V_CERTSTATUS_REVOKED
70 puts "Certificate #{domains.first} on #{host} has been revoked"
73 subject_alt_name = certificate.extensions.find { |ext| ext.oid == "subjectAltName" }
75 if subject_alt_name.nil?
76 puts "Certificate #{domains.first} on #{host} has no subjectAltName"
78 alt_names = subject_alt_name.value.split(/\s*,\s*/).map { |n| n.sub(/^DNS:/, "") }
80 domains.each do |domain|
81 if alt_names.include?(domain)
82 alt_names.delete(domain)
84 puts "Certificate #{domains.first} on #{host} is missing subjectAltName #{domain}"
88 alt_names.each do |name|
89 puts "Certificate #{domains.first} on #{host} has unexpected subjectAltName #{name}"