Introduction

OpenSSL certificate verification fails when the certificate chain cannot be validated against trusted root certificates. The openssl verify command returns error codes that indicate specific problems - missing intermediates, untrusted roots, expired certificates, or hostname mismatches. Understanding these error codes helps pinpoint and fix certificate configuration issues.

Symptoms

  • openssl verify returns non-zero exit code
  • Error: verify error:num=20:unable to get local issuer certificate
  • Error: verify error:num=21:unable to verify the first certificate
  • Error: verify error:num=27:certificate not trusted
  • Error: verify error:num=10:certificate has expired
  • OpenSSL s_client shows verification errors
  • Applications using OpenSSL fail certificate checks

Common Causes

  • Missing intermediate certificates in chain
  • Root CA not in trust store
  • Certificate chain order wrong
  • Certificate expired or not yet valid
  • Self-signed certificate not trusted
  • Hostname doesn't match certificate
  • Wrong CA bundle file used
  • Trust store outdated or missing roots

Step-by-Step Fix

Step 1: Run Basic Verification

```bash # Verify certificate against default trust store openssl verify server.crt

# Verify with explicit CA bundle openssl verify -CAfile /etc/ssl/certs/ca-bundle.crt server.crt

# Check specific error code openssl verify -verbose server.crt

# Common error codes: # 20: unable to get local issuer certificate (missing intermediate) # 21: unable to verify the first certificate (chain incomplete) # 27: certificate not trusted (root not in trust store) # 10: certificate has expired # 18: self signed certificate # 24: invalid CA certificate ```

Step 2: Check Certificate Chain

```bash # Get certificate chain from server openssl s_client -connect example.com:443 -showcerts 2>/dev/null

# Save each certificate in chain openssl s_client -connect example.com:443 -showcerts 2>/dev/null < /dev/null | \ awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/' > fullchain.pem

# Check chain order openssl verify -CAfile intermediate.crt -untrusted intermediate.crt server.crt ```

Step 3: Fix Missing Intermediate

```bash # If error 20 or 21, intermediate missing

# Find certificate issuer openssl x509 -in server.crt -noout -issuer

# Download intermediate from CA # Check AIA extension for intermediate URL openssl x509 -in server.crt -noout -text | grep -A 4 "Authority Information Access"

# Create full chain cat server.crt intermediate.crt > fullchain.crt

# Verify full chain openssl verify -CAfile /etc/ssl/certs/ca-bundle.crt fullchain.crt ```

Step 4: Fix Trust Store Issues

```bash # If error 27, root CA not trusted

# Check trust store location # Common locations: # /etc/ssl/certs/ca-bundle.crt (many Linux) # /etc/pki/tls/certs/ca-bundle.crt (RHEL/CentOS) # /usr/local/share/certs/ca-root-nss.crt (FreeBSD) # /etc/ssl/cert.pem (Alpine, macOS)

# Check if root exists openssl x509 -in root.crt -noout -subject grep -r "$(openssl x509 -in root.crt -noout -subject)" /etc/ssl/certs/

# Add root to trust store cp root.crt /usr/local/share/ca-certificates/ update-ca-certificates

# Or on RHEL/CentOS cp root.crt /etc/pki/ca-trust/source/anchors/ update-ca-trust ```

Step 5: Verify Certificate Validity

```bash # Check certificate dates openssl x509 -in server.crt -noout -dates

# Check if currently valid openssl x509 -in server.crt -noout -checkend 0

# Returns 0 if valid, 1 if expired or not yet valid

# Check specific time validity openssl x509 -in server.crt -noout -text | grep -A 2 Validity ```

Step 6: Verify Hostname Match

```bash # Check certificate hostname openssl x509 -in server.crt -noout -subject openssl x509 -in server.crt -noout -text | grep -A 1 "Subject Alternative Name"

# Verify hostname explicitly openssl verify -CAfile /etc/ssl/certs/ca-bundle.crt \ -verify_hostname example.com server.crt

# Or with s_client openssl s_client -connect example.com:443 -servername example.com 2>&1 | \ grep "Verify return" ```

Step 7: Verify Chain Order

```bash # Chain must be: server cert, then intermediates, then optionally root # OpenSSL verify needs correct order

# Split and reassemble in correct order # Extract each cert csplit -z fullchain.pem '/-----BEGIN CERTIFICATE-----/' '{*}'

# Verify order: first cert is server, next are intermediates openssl x509 -in xx00 -noout -subject # Should be your server openssl x509 -in xx01 -noout -subject # Should be intermediate

# Reassemble in order cat xx00 xx01 xx02 > ordered-chain.pem ```

Step 8: Comprehensive Verification

```bash # Full verification chain openssl verify -CAfile /etc/ssl/certs/ca-bundle.crt \ -untrusted intermediate.crt \ -verbose server.crt

# Test actual connection openssl s_client -connect example.com:443 \ -servername example.com \ -CAfile /etc/ssl/certs/ca-bundle.crt

# Check return code echo | openssl s_client -connect example.com:443 2>&1 | grep "Verify return" # Should show: Verify return code: 0 (ok) ```

Step 9: Use SSL Labs for External Verification

```bash # SSL Labs provides comprehensive chain analysis # https://www.ssllabs.com/ssltest/analyze.html?d=example.com

# Or use API curl -s "https://api.ssllabs.com/api/v3/analyze?host=example.com&publish=on" | jq ```

Common Pitfalls

  • Using wrong CA bundle file location
  • Not including intermediate in verification
  • Chain order reversed (root before server)
  • Trust store missing modern root CAs
  • Testing with outdated OpenSSL version
  • Not checking hostname in verification
  • Self-signed cert treated as normal CA-signed

Best Practices

  • Always verify full chain, not just server cert
  • Use -showcerts to capture complete chain from server
  • Keep trust store updated with latest root CAs
  • Document expected chain structure for your certificates
  • Verify hostname matching explicitly
  • Test with both OpenSSL and SSL Labs
  • Use -verbose flag for detailed error diagnosis
  • SSL Certificate Chain Incomplete
  • SSL Certificate Not Trusted
  • SSL Certificate Expired
  • SSL Private Key Mismatch