require "openssl" cert = OpenSSL::X509::Certificate.new(File.binread(ARGV.fetch(0, "cert.pem"))) # PEM or DER puts "subject: #{cert.subject}" # OpenSSL's one-line formputs "common name: #{cert.subject.to_a.find { |name, _, _| name == 'CN' }&.at(1)}"puts "issuer: #{cert.issuer.to_s(OpenSSL::X509::Name::RFC2253)}"puts "serial: #{cert.serial.to_s(16)}"puts "valid: #{cert.not_before.utc.iso8601} -> #{cert.not_after.utc.iso8601}"puts "signed with: #{cert.signature_algorithm}" # the issuer's algorithm # extensions come back as OpenSSL's text, e.g. "DNS:a, DNS:b"san = cert.extensions.find { |extension| extension.oid == "subjectAltName" }puts "DNS names: #{san.value.split(', ').filter_map { |name| name.delete_prefix('DNS:') if name.start_with?('DNS:') }.join(' ')}" if san key = cert.public_keyputs "key: EC on #{key.group.curve_name}" if key.is_a?(OpenSSL::PKey::EC)puts "SHA-256 fingerprint: #{OpenSSL::Digest::SHA256.hexdigest(cert.to_der).scan(/../).join(':').upcase}"puts "days until expiry: #{((cert.not_after - Time.now) / 86_400).floor}" # Decoding proves nothing about trust; a store with your trust anchors doesstore = OpenSSL::X509::Store.newstore.add_file("ca.pem") # or store.set_default_paths for the system rootsleaf = OpenSSL::X509::Certificate.new(File.read("leaf.pem"))puts "leaf trusted: #{store.verify(leaf)}" untrusted = OpenSSL::X509::Certificate.new(File.read("cert.pem"))puts "example trusted: #{store.verify(untrusted)} (#{store.error_string})" How it works
OpenSSL::X509::Certificate.newdetects PEM or DER from the bytes;File.binreadkeeps DER intact.subjectprints OpenSSL's slash form,to_agives[name, value, type]triples for picking attributes such asCN, andto_s(OpenSSL::X509::Name::RFC2253)the comma-separated RFC 2253 form.not_beforeandnot_afterareTimeobjects, so expiry is plain arithmetic;signature_algorithmis the issuer's algorithm, likeecdsa-with-SHA256.- Each extension has
oid,valueandcritical?;valueis OpenSSL's text, so SAN names are split fromDNS:a, DNS:b. OpenSSL::X509::Store#verifybuilds a chain to the anchors you added;error_stringexplains a failure, such asself-signed certificatefor the example.
Gotchas
- RFC 2253 output is in reverse order —
C=US,O=…,CN=demo.ecdsa.comhere, while the defaultto_sand OpenSSL's CLI start withCN. CompareOpenSSL::X509::Nameobjects (orto_a) rather than formatted strings. - Extension values are display text whose exact wording comes from OpenSSL and can change between versions. For key usage or basic constraints in security decisions, rely on
Store#verifyand its purpose settings instead of parsing the text. OpenSSL::Digest::SHA256.hexdigest(cert.to_der)is the certificate fingerprint; the fingerprint of its key is a different value — see the fingerprint recipe.store.set_default_pathsloads the system roots; in slim containers they exist only when theca-certificatespackage is installed, which is the usual cause ofunable to get local issuer certificatein Docker.