Skip to content
ecdsa.com

Verify a certificate chain · Ruby

How to verify a certificate chain against your own trust store in Ruby

To verify a certificate chain against your own trust store in Ruby, add your root to an OpenSSL::X509::Store and call store.verify(leaf, intermediates): it returns true for a trusted chain, and store.chain and store.error_string tell you the path or the reason. This recipe checks a leaf → intermediate → root chain from a test CA and three chains that must fail.

Tested with openssl (Ruby gem) 4.0.2 (Ruby 4.0.7, OpenSSL 3.5.7).

verify.rb — chain validation with OpenSSL::X509::Store
require "openssl"
def load(path) = OpenSSL::X509::Certificate.new(File.read(path))
def verify_chain(leaf, intermediates, root)
store = OpenSSL::X509::Store.new
store.add_cert(root) # trusted; store.set_default_paths would add the system roots
store.purpose = OpenSSL::X509::PURPOSE_SSL_SERVER
ok = store.verify(leaf, intermediates) # intermediates: untrusted candidates for the path
[ok, ok ? store.chain.map { |c| c.subject.to_a.find { |n, _, _| n == "CN" }[1] } : store.error_string]
end
root = load("root.pem")
intermediate = load("intermediate.pem")
{
"good chain" => [load("leaf.pem"), [intermediate], root],
"impostor root" => [load("leaf.pem"), [intermediate], load("impostor-root.pem")],
"expired leaf" => [load("expired.pem"), [intermediate], root],
"missing intermediate" => [load("leaf.pem"), [], root],
}.each do |label, (leaf, intermediates, trusted)|
ok, detail = verify_chain(leaf, intermediates, trusted)
puts "#{label}: #{ok} #{ok ? detail.join(' -> ') : "(#{detail})"}"
end

How it works

  1. store.add_cert(root) adds a trust anchor; store.set_default_paths would add the system roots instead.
  2. store.purpose = OpenSSL::X509::PURPOSE_SSL_SERVER requires certificates fit for a TLS server along the path.
  3. store.verify(leaf, intermediates) builds the path through the untrusted intermediates and returns true or false.
  4. After a successful check, store.chain is the path: api.example.com → ECDSA.com Howto Issuing CA 1 → ECDSA.com Howto Root CA.

Gotchas

  • After a failure, read store.error_string (and store.error for the number): unable to get local issuer certificate for both the look-alike root and the missing intermediate, certificate has expired for the expired leaf.
  • verify returns false rather than raising, so a forgotten check of the return value accepts every chain.
  • store.time = Time.utc(2025, 2, 1) validates at another moment — the expired leaf then verifies true — which suits old signatures, never as a workaround for an expired certificate.

Related recipes