openssl: Certificate & Cryptography Operations

Every secure connection in your infrastructure relies on certificates. openssl is how you inspect, verify, debug, and manage them.


Core Concepts

The Certificate Ecosystem

┌─────────────────────────────────────────────────────────────────┐
│                    PKI TRUST HIERARCHY                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│                    ┌──────────────┐                              │
│                    │   ROOT CA    │  (Self-signed, offline)      │
│                    │  DOMUS-ROOT  │                              │
│                    └──────┬───────┘                              │
│                           │ signs                                │
│                           ▼                                      │
│                    ┌──────────────┐                              │
│                    │ ISSUING CA   │  (Issues end-entity certs)   │
│                    │DOMUS-ISSUING │                              │
│                    └──────┬───────┘                              │
│                           │ signs                                │
│              ┌────────────┼────────────┐                         │
│              ▼            ▼            ▼                         │
│         ┌────────┐   ┌────────┐   ┌────────┐                    │
│         │ ise-01 │   │ vault  │   │ client │  (End-entity)      │
│         │ server │   │ server │   │  cert  │                    │
│         └────────┘   └────────┘   └────────┘                    │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Certificate Components

Component Purpose

Subject

Who this certificate identifies (CN, O, OU)

Issuer

Who signed this certificate (the CA)

Validity

Not Before / Not After timestamps

Serial

Unique identifier from the CA

SANs

Subject Alternative Names (DNS, IP, email)

Key Usage

What the cert can be used for (signing, encryption)

Extended Key Usage

Specific purposes (server auth, client auth, code signing)

Signature

CA’s cryptographic signature proving authenticity


Remote Certificate Inspection

Basic Connection

# Connect and show certificate
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -text

# TL;DR version - just the important fields
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -subject -issuer -dates -serial

Output:

subject=CN=ise-01.inside.domusdigitalis.dev
issuer=CN=DOMUS-ISSUING-CA
notBefore=Jan  1 00:00:00 2026 GMT
notAfter=Jan  1 00:00:00 2027 GMT
serial=0A1B2C3D4E5F

With SNI (Server Name Indication)

Required when server hosts multiple certificates:

# Explicit servername for SNI
echo | openssl s_client -connect 10.50.1.20:443 \
  -servername ise-01.inside.domusdigitalis.dev 2>/dev/null | \
  openssl x509 -noout -subject

Non-Standard Ports

# Vault (8200)
echo | openssl s_client -connect vault.inside.domusdigitalis.dev:8200 2>/dev/null | \
  openssl x509 -noout -subject -dates

# LDAPS (636)
echo | openssl s_client -connect home-dc01.inside.domusdigitalis.dev:636 2>/dev/null | \
  openssl x509 -noout -subject -dates

# ISE ERS API (9060)
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:9060 2>/dev/null | \
  openssl x509 -noout -subject -dates

STARTTLS Protocols

For protocols that upgrade to TLS:

# SMTP
echo | openssl s_client -connect mail.example.com:25 -starttls smtp 2>/dev/null | \
  openssl x509 -noout -subject -dates

# LDAP (not LDAPS)
echo | openssl s_client -connect dc.example.com:389 -starttls ldap 2>/dev/null | \
  openssl x509 -noout -subject -dates

# IMAP
echo | openssl s_client -connect mail.example.com:143 -starttls imap 2>/dev/null | \
  openssl x509 -noout -subject -dates

# FTP
echo | openssl s_client -connect ftp.example.com:21 -starttls ftp 2>/dev/null | \
  openssl x509 -noout -subject -dates

Certificate Field Extraction

Subject and Issuer

# Full subject line
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -subject

# Just the CN (Common Name)
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -subject | sed 's/.*CN=//' | cut -d',' -f1

# With awk (more robust)
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -subject | awk -F'CN=' '{print $2}' | awk -F',' '{print $1}'

Validity Dates

# Both dates
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -dates

# Just expiry
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -enddate

# Expiry as epoch (for calculations)
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -enddate | cut -d= -f2 | xargs -I{} date -d "{}" +%s

Days Until Expiry

# Calculate days remaining
expire_date=$(echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -enddate | cut -d= -f2)
expire_epoch=$(date -d "$expire_date" +%s)
now_epoch=$(date +%s)
days_left=$(( (expire_epoch - now_epoch) / 86400 ))
echo "$days_left days until expiry"

# One-liner
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -enddate | cut -d= -f2 | \
  xargs -I{} bash -c 'echo $(( ($(date -d "{}" +%s) - $(date +%s)) / 86400 )) days'

Subject Alternative Names (SANs)

# Show SAN extension
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -ext subjectAltName

# Extract DNS names only
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -ext subjectAltName 2>/dev/null | \
  tr ',' '\n' | grep DNS: | sed 's/.*DNS://'

# Extract IP addresses
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -ext subjectAltName 2>/dev/null | \
  tr ',' '\n' | grep IP: | sed 's/.*IP Address://'

Key Usage and Extended Key Usage

# Key usage (what the key can do)
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -ext keyUsage

# Extended key usage (specific purposes)
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -ext extendedKeyUsage

# Example output:
# X509v3 Extended Key Usage:
#     TLS Web Server Authentication, TLS Web Client Authentication

Fingerprints

# SHA-256 fingerprint (modern)
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -fingerprint -sha256

# SHA-1 fingerprint (legacy, still used by some systems)
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -fingerprint -sha1

# Just the hash value
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  openssl x509 -noout -fingerprint -sha256 | cut -d= -f2

Certificate Chain Operations

View Full Chain

# Show all certificates in chain
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 -showcerts 2>/dev/null

# Count certificates in chain
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 -showcerts 2>/dev/null | \
  grep -c "BEGIN CERTIFICATE"

Extract Each Certificate

# Save chain to file, split into individual certs
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 -showcerts 2>/dev/null | \
  awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/ {print}' > chain.pem

# Split into separate files
csplit -s -z chain.pem '/-----BEGIN CERTIFICATE-----/' '{*}'

# Inspect each
for cert in xx*; do
  echo "=== $cert ==="
  openssl x509 -in "$cert" -noout -subject -issuer
done

# Cleanup
rm -f xx* chain.pem

Verify Chain

# Verify certificate against CA
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt server.pem

# Verify with intermediate
openssl verify -CAfile root.pem -untrusted intermediate.pem server.pem

# Verify remote (download and check)
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 -showcerts 2>/dev/null | \
  openssl verify -CAfile /path/to/ca.pem

Local Certificate Files

Read Different Formats

# PEM format (base64, most common)
openssl x509 -in cert.pem -noout -text

# DER format (binary)
openssl x509 -in cert.der -inform DER -noout -text

# PKCS#12 / PFX (certificate + private key bundle)
openssl pkcs12 -in cert.p12 -nokeys -clcerts 2>/dev/null | \
  openssl x509 -noout -subject -dates

# PKCS#7 (certificate chain)
openssl pkcs7 -in cert.p7b -print_certs | \
  openssl x509 -noout -subject -dates

Convert Between Formats

# PEM to DER
openssl x509 -in cert.pem -outform DER -out cert.der

# DER to PEM
openssl x509 -in cert.der -inform DER -outform PEM -out cert.pem

# PKCS#12 to PEM (extract cert)
openssl pkcs12 -in cert.p12 -clcerts -nokeys -out cert.pem

# PKCS#12 to PEM (extract key)
openssl pkcs12 -in cert.p12 -nocerts -nodes -out key.pem

# PEM cert + key to PKCS#12
openssl pkcs12 -export -in cert.pem -inkey key.pem -out cert.p12

Verify Key Matches Certificate

# Compare modulus (RSA) - must match
openssl x509 -in cert.pem -noout -modulus | openssl md5
openssl rsa -in key.pem -noout -modulus | openssl md5

# Compare public key (works for all key types)
openssl x509 -in cert.pem -noout -pubkey | openssl md5
openssl pkey -in key.pem -pubout | openssl md5

# One-liner check
diff <(openssl x509 -in cert.pem -noout -pubkey) \
     <(openssl pkey -in key.pem -pubout) && echo "MATCH" || echo "MISMATCH"

Infrastructure Automation

Bulk Certificate Audit

#!/bin/bash
# cert-audit.sh - Audit all infrastructure certificates

HOSTS=(
  "ise-01.inside.domusdigitalis.dev:443"
  "ise-02.inside.domusdigitalis.dev:443"
  "vault.inside.domusdigitalis.dev:8200"
  "pfsense.inside.domusdigitalis.dev:443"
  "home-dc01.inside.domusdigitalis.dev:636"
  "nas-01.inside.domusdigitalis.dev:5001"
  "wlc.inside.domusdigitalis.dev:443"
  "keycloak.inside.domusdigitalis.dev:8443"
)

THRESHOLD=${1:-30}  # Days until expiry to warn

echo "Certificate Audit - $(date)"
echo "Alert threshold: $THRESHOLD days"
echo "================================================"
printf "%-45s %-12s %-20s %s\n" "HOST" "DAYS LEFT" "EXPIRES" "STATUS"
echo "------------------------------------------------"

for hostport in "${HOSTS[@]}"; do
  host="${hostport%%:*}"

  # Fetch certificate
  result=$(echo | timeout 5 openssl s_client -connect "$hostport" \
    -servername "$host" 2>/dev/null | \
    openssl x509 -noout -enddate 2>/dev/null)

  if [[ -z "$result" ]]; then
    printf "%-45s %-12s %-20s %s\n" "$hostport" "N/A" "N/A" "UNREACHABLE"
    continue
  fi

  # Parse expiry
  enddate=$(echo "$result" | cut -d= -f2)
  expire_epoch=$(date -d "$enddate" +%s)
  now_epoch=$(date +%s)
  days=$(( (expire_epoch - now_epoch) / 86400 ))

  # Determine status
  if [[ $days -lt 0 ]]; then
    status="EXPIRED"
  elif [[ $days -lt $THRESHOLD ]]; then
    status="WARNING"
  else
    status="OK"
  fi

  printf "%-45s %-12s %-20s %s\n" "$hostport" "$days" "$enddate" "$status"
done

JSON Output for Monitoring

#!/bin/bash
# cert-audit-json.sh - JSON output for integration with monitoring

HOSTS=(
  "ise-01.inside.domusdigitalis.dev:443"
  "ise-02.inside.domusdigitalis.dev:443"
  "vault.inside.domusdigitalis.dev:8200"
)

audit_cert() {
  local hostport="$1"
  local host="${hostport%%:*}"

  local data=$(echo | timeout 5 openssl s_client -connect "$hostport" \
    -servername "$host" 2>/dev/null | \
    openssl x509 -noout -subject -issuer -enddate -startdate 2>/dev/null)

  if [[ -z "$data" ]]; then
    jq -n --arg h "$hostport" '{host: $h, error: "unreachable"}'
    return
  fi

  local subject=$(echo "$data" | grep subject | sed 's/subject=//' | xargs)
  local issuer=$(echo "$data" | grep issuer | sed 's/issuer=//' | xargs)
  local notafter=$(echo "$data" | grep notAfter | cut -d= -f2)
  local notbefore=$(echo "$data" | grep notBefore | cut -d= -f2)

  local expire_epoch=$(date -d "$notafter" +%s)
  local now_epoch=$(date +%s)
  local days=$(( (expire_epoch - now_epoch) / 86400 ))

  jq -n \
    --arg host "$hostport" \
    --arg subject "$subject" \
    --arg issuer "$issuer" \
    --arg expires "$notafter" \
    --arg issued "$notbefore" \
    --argjson days_left "$days" \
    '{
      host: $host,
      subject: $subject,
      issuer: $issuer,
      issued: $issued,
      expires: $expires,
      days_left: $days_left,
      status: (if $days_left < 0 then "expired"
               elif $days_left < 30 then "warning"
               else "ok" end)
    }'
}

# Run audit
for hostport in "${HOSTS[@]}"; do
  audit_cert "$hostport"
done | jq -s '{
  audit_time: (now | strftime("%Y-%m-%dT%H:%M:%SZ")),
  certificates: .,
  summary: {
    total: (. | length),
    ok: ([.[] | select(.status == "ok")] | length),
    warning: ([.[] | select(.status == "warning")] | length),
    expired: ([.[] | select(.status == "expired")] | length),
    unreachable: ([.[] | select(.error)] | length)
  }
}'

Compare Against Expected Values

#!/bin/bash
# cert-validate.sh - Verify certs match expected configuration

declare -A EXPECTED_ISSUER=(
  ["ise-01.inside.domusdigitalis.dev:443"]="DOMUS-ISSUING-CA"
  ["vault.inside.domusdigitalis.dev:8200"]="DOMUS-ISSUING-CA"
  ["home-dc01.inside.domusdigitalis.dev:636"]="DOMUS-ROOT-CA"
)

for hostport in "${!EXPECTED_ISSUER[@]}"; do
  host="${hostport%%:*}"
  expected="${EXPECTED_ISSUER[$hostport]}"

  actual=$(echo | timeout 5 openssl s_client -connect "$hostport" \
    -servername "$host" 2>/dev/null | \
    openssl x509 -noout -issuer | grep -oP 'CN=\K[^,]+')

  if [[ "$actual" == "$expected" ]]; then
    echo "✓ $hostport: Issuer OK ($actual)"
  else
    echo "✗ $hostport: Issuer MISMATCH (expected: $expected, got: $actual)"
  fi
done

TLS Debugging

Protocol and Cipher Information

# Show negotiated protocol and cipher
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  grep -E "(Protocol|Cipher)"

# Test specific TLS version
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 \
  -tls1_2 2>/dev/null | grep -E "(Protocol|Cipher)"

echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 \
  -tls1_3 2>/dev/null | grep -E "(Protocol|Cipher)"

Enumerate Supported Ciphers

# List all ciphers server accepts (brute force)
for cipher in $(openssl ciphers 'ALL:eNULL' | tr ':' '\n'); do
  result=$(echo | timeout 2 openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 \
    -cipher "$cipher" 2>/dev/null)
  if [[ $? -eq 0 ]]; then
    echo "$cipher: SUPPORTED"
  fi
done 2>/dev/null

# Using nmap (faster)
nmap --script ssl-enum-ciphers -p 443 ise-01.inside.domusdigitalis.dev

Check for Weak Configurations

# Test for SSLv3 (should fail)
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 \
  -ssl3 2>&1 | grep -q "ssl handshake failure" && echo "SSLv3: DISABLED (good)" || echo "SSLv3: ENABLED (bad)"

# Test for TLS 1.0 (should fail in modern configs)
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 \
  -tls1 2>&1 | grep -q "ssl handshake failure" && echo "TLS 1.0: DISABLED (good)" || echo "TLS 1.0: ENABLED (review)"

# Test for weak ciphers
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 \
  -cipher 'NULL:EXPORT:LOW:DES:RC4' 2>&1 | grep -q "handshake failure" && \
  echo "Weak ciphers: DISABLED (good)" || echo "Weak ciphers: ENABLED (bad)"

Full Handshake Debug

# Verbose connection debug
openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 \
  -servername ise-01.inside.domusdigitalis.dev \
  -state -debug 2>&1 | head -100

# Show session details
echo | openssl s_client -connect ise-01.inside.domusdigitalis.dev:443 2>/dev/null | \
  grep -A20 "SSL-Session:"

Key Operations

Generate Keys

# RSA 4096-bit
openssl genrsa -out key.pem 4096

# RSA with passphrase
openssl genrsa -aes256 -out key.pem 4096

# ECDSA P-256 (faster, modern)
openssl ecparam -genkey -name prime256v1 -noout -out key.pem

# ECDSA P-384
openssl ecparam -genkey -name secp384r1 -noout -out key.pem

# Ed25519 (newest, fastest)
openssl genpkey -algorithm ED25519 -out key.pem

Inspect Keys

# RSA key details
openssl rsa -in key.pem -noout -text

# EC key details
openssl ec -in key.pem -noout -text

# Check key type
openssl pkey -in key.pem -noout -text | head -1

Generate CSR

# Interactive
openssl req -new -key key.pem -out request.csr

# Non-interactive (for automation)
openssl req -new -key key.pem -out request.csr \
  -subj "/C=US/ST=California/L=Los Angeles/O=CHLA/OU=InfoSec/CN=host.example.com"

# With SANs (requires config file or OpenSSL 1.1.1+)
openssl req -new -key key.pem -out request.csr \
  -subj "/CN=host.example.com" \
  -addext "subjectAltName=DNS:host.example.com,DNS:host,IP:10.50.1.100"

Inspect CSR

# Full CSR details
openssl req -in request.csr -noout -text

# Just subject
openssl req -in request.csr -noout -subject

# Verify CSR signature
openssl req -in request.csr -noout -verify

Quick Reference

Task Command

Remote cert info

echo | openssl s_client -connect host:443 2>/dev/null | openssl x509 -noout -text

Subject/Issuer

openssl x509 -noout -subject -issuer

Expiry dates

openssl x509 -noout -dates

SANs

openssl x509 -noout -ext subjectAltName

Fingerprint

openssl x509 -noout -fingerprint -sha256

Full chain

openssl s_client -showcerts

Verify cert

openssl verify -CAfile ca.pem cert.pem

Key matches cert

Compare openssl x509 -pubkey vs openssl pkey -pubout

PEM to DER

openssl x509 -outform DER

Extract from PKCS12

openssl pkcs12 -in file.p12 -clcerts -nokeys

Generate RSA key

openssl genrsa -out key.pem 4096

Generate EC key

openssl ecparam -genkey -name prime256v1 -noout

Create CSR

openssl req -new -key key.pem -out req.csr

Self-sign cert

openssl x509 -req -in req.csr -signkey key.pem -out cert.pem