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"
Quick Reference
| Task | Command |
|---|---|
Remote cert info |
|
Subject/Issuer |
|
Expiry dates |
|
SANs |
|
Fingerprint |
|
Full chain |
|
Verify cert |
|
Key matches cert |
Compare |
PEM to DER |
|
Extract from PKCS12 |
|
Generate RSA key |
|
Generate EC key |
|
Create CSR |
|
Self-sign cert |
|
Related
-
curl - HTTPS connections use these certs
-
Infrastructure APIs - Certificate monitoring patterns