SSL/TLS Certificates
Quick Reference
# Generate self-signed certificate
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
# Generate CSR (Certificate Signing Request)
openssl req -new -newkey rsa:4096 -keyout key.pem -out request.csr -nodes
# View certificate details
openssl x509 -in cert.pem -text -noout
# Verify certificate chain
openssl verify -CAfile ca-bundle.crt cert.pem
# Check certificate expiration
openssl x509 -in cert.pem -noout -enddate
# Test SSL connection
openssl s_client -connect example.com:443 -servername example.com
# Convert formats
openssl pkcs12 -export -out cert.p12 -inkey key.pem -in cert.pem
openssl pkcs12 -in cert.p12 -out cert.pem -nodes
Understanding SSL/TLS Certificates
What is a Certificate?
An SSL/TLS certificate is a digital document that:
-
Binds a public key to an identity (domain, organization, person)
-
Proves the identity has been verified by a trusted authority
-
Enables encrypted communication between parties
-
Provides authentication, integrity, and confidentiality
Certificate Chain of Trust
Certificates form a chain of trust from a trusted Root CA down to end-entity certificates:
-
Root CA - Self-signed, stored in system trust stores, issues intermediate CAs
-
Intermediate CA - Signed by Root CA, issues end-entity certificates
-
End-Entity Certificates - Server certs, client certs, code signing certs
X.509 Certificate Fields
An X.509v3 certificate contains:
-
Version - Certificate format version (usually 3)
-
Serial Number - Unique identifier from the issuing CA
-
Signature Algorithm - Algorithm used to sign (e.g., sha256WithRSAEncryption)
-
Issuer - Distinguished Name of the CA that issued the certificate
-
Validity - Not Before and Not After dates
-
Subject - Distinguished Name of the certificate owner
-
Public Key - Subject’s public key and algorithm
-
Extensions - SAN, Key Usage, Basic Constraints, etc.
-
Signature - CA’s digital signature over the certificate
Certificate Management
System Certificate Stores
Debian/Ubuntu
# System CA certificates location
/etc/ssl/certs/ # Individual CA certs
/etc/ssl/certs/ca-certificates.crt # Bundle file
# Add custom CA
sudo cp my-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
# Remove CA
sudo rm /usr/local/share/ca-certificates/my-ca.crt
sudo update-ca-certificates --fresh
# List installed CAs
ls /etc/ssl/certs/ | head -20
RHEL/CentOS/Fedora
# System CA certificates location
/etc/pki/tls/certs/ # Certificates
/etc/pki/tls/private/ # Private keys
/etc/pki/ca-trust/source/anchors/ # Custom CAs
# Add custom CA
sudo cp my-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust extract
# View trust settings
trust list
# Add with specific trust
trust anchor --store my-ca.crt
Application-Specific Stores
# Java keystore
$JAVA_HOME/lib/security/cacerts
keytool -list -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit
# Add to Java keystore
keytool -import -trustcacerts -file my-ca.crt \
-alias myca -keystore $JAVA_HOME/lib/security/cacerts
# Firefox (uses NSS)
~/.mozilla/firefox/*.default/cert9.db
certutil -A -d sql:$HOME/.mozilla/firefox/*.default -t "C,," -n "My CA" -i my-ca.crt
# Chrome (uses NSS on Linux)
~/.pki/nssdb/
certutil -A -d sql:$HOME/.pki/nssdb -t "C,," -n "My CA" -i my-ca.crt
OpenSSL Operations
Generate Keys
# RSA key (4096-bit recommended)
openssl genrsa -out private.key 4096
# RSA key with passphrase
openssl genrsa -aes256 -out private.key 4096
# ECDSA key (P-384 curve)
openssl ecparam -genkey -name secp384r1 -out private.key
# Ed25519 key (modern, fast)
openssl genpkey -algorithm ED25519 -out private.key
# View key details
openssl rsa -in private.key -text -noout
openssl ec -in private.key -text -noout
Generate Certificate Signing Request (CSR)
# Interactive CSR generation
openssl req -new -key private.key -out request.csr
# Non-interactive with subject
openssl req -new -key private.key -out request.csr \
-subj "/C=US/ST=California/L=San Francisco/O=My Company/CN=example.com"
# CSR with SAN (Subject Alternative Names)
cat > csr.conf << 'EOF'
[req]
default_bits = 4096
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = req_ext
[dn]
C = US
ST = California
L = San Francisco
O = My Company
CN = example.com
[req_ext]
subjectAltName = @alt_names
[alt_names]
DNS.1 = example.com
DNS.2 = www.example.com
DNS.3 = api.example.com
IP.1 = 192.168.1.100
EOF
openssl req -new -key private.key -out request.csr -config csr.conf
# View CSR
openssl req -in request.csr -text -noout
Self-Signed Certificates
# Quick self-signed (development)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \
-days 365 -nodes \
-subj "/CN=localhost"
# Self-signed with SAN
cat > cert.conf << 'EOF'
[req]
default_bits = 4096
prompt = no
default_md = sha256
x509_extensions = v3_req
distinguished_name = dn
[dn]
C = US
ST = California
L = San Francisco
O = Development
CN = localhost
[v3_req]
subjectAltName = @alt_names
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
[alt_names]
DNS.1 = localhost
DNS.2 = *.localhost
IP.1 = 127.0.0.1
IP.2 = ::1
EOF
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \
-days 365 -nodes -config cert.conf
Create Certificate Authority
# Create CA directory structure
mkdir -p ca/{certs,crl,newcerts,private}
chmod 700 ca/private
touch ca/index.txt
echo 1000 > ca/serial
# Generate CA key
openssl genrsa -aes256 -out ca/private/ca.key 4096
chmod 400 ca/private/ca.key
# Create CA certificate
cat > ca/ca.conf << 'EOF'
[req]
default_bits = 4096
prompt = no
default_md = sha256
distinguished_name = dn
x509_extensions = v3_ca
[dn]
C = US
ST = California
O = My Organization
CN = My Root CA
[v3_ca]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
EOF
openssl req -x509 -new -nodes -key ca/private/ca.key \
-sha256 -days 3650 -out ca/certs/ca.crt -config ca/ca.conf
# Sign a CSR with CA
cat > ca/sign.conf << 'EOF'
[ca]
default_ca = CA_default
[CA_default]
dir = ./ca
certs = $dir/certs
new_certs_dir = $dir/newcerts
database = $dir/index.txt
serial = $dir/serial
private_key = $dir/private/ca.key
certificate = $dir/certs/ca.crt
default_md = sha256
default_days = 365
policy = policy_loose
[policy_loose]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[server_cert]
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
EOF
openssl ca -config ca/sign.conf -extensions server_cert \
-notext -md sha256 -in request.csr -out signed.crt
Format Conversions
PEM Conversions
# PEM to DER
openssl x509 -in cert.pem -outform DER -out cert.der
# DER to PEM
openssl x509 -in cert.der -inform DER -out cert.pem
# Extract certificate from PKCS#12
openssl pkcs12 -in cert.p12 -clcerts -nokeys -out cert.pem
# Extract private key from PKCS#12
openssl pkcs12 -in cert.p12 -nocerts -nodes -out key.pem
# Create PKCS#12 from PEM
openssl pkcs12 -export -out cert.p12 \
-inkey key.pem -in cert.pem -certfile ca-chain.pem
# Extract CA chain from PKCS#12
openssl pkcs12 -in cert.p12 -cacerts -nokeys -out ca-chain.pem
Key Conversions
# Remove passphrase from key
openssl rsa -in encrypted.key -out decrypted.key
# Add passphrase to key
openssl rsa -in decrypted.key -aes256 -out encrypted.key
# Convert RSA to PKCS#8
openssl pkcs8 -topk8 -inform PEM -outform PEM \
-in rsa.key -out pkcs8.key -nocrypt
# Extract public key from private
openssl rsa -in private.key -pubout -out public.key
# Extract public key from certificate
openssl x509 -in cert.pem -pubkey -noout > public.key
Certificate Verification
Inspect Certificates
# View full certificate
openssl x509 -in cert.pem -text -noout
# View specific fields
openssl x509 -in cert.pem -noout -subject
openssl x509 -in cert.pem -noout -issuer
openssl x509 -in cert.pem -noout -dates
openssl x509 -in cert.pem -noout -serial
openssl x509 -in cert.pem -noout -fingerprint -sha256
# View SAN
openssl x509 -in cert.pem -noout -ext subjectAltName
# Check key size
openssl x509 -in cert.pem -noout -text | grep "Public-Key"
# View extensions
openssl x509 -in cert.pem -noout -ext basicConstraints,keyUsage,extendedKeyUsage
Verify Certificate Chain
# Verify against system CA store
openssl verify cert.pem
# Verify against specific CA
openssl verify -CAfile ca.crt cert.pem
# Verify full chain
openssl verify -CAfile root.crt -untrusted intermediate.crt cert.pem
# Verify certificate chain file
openssl verify -CAfile ca-chain.crt cert.pem
# Show chain of trust
openssl crl2pkcs7 -nocrl -certfile chain.pem | \
openssl pkcs7 -print_certs -noout
Check Certificate Expiration
# Show expiration date
openssl x509 -in cert.pem -noout -enddate
# Check if valid in N days
openssl x509 -in cert.pem -checkend 86400 # 86400 = 1 day in seconds
# Script: check multiple certificates
for cert in /etc/ssl/certs/*.pem; do
expiry=$(openssl x509 -in "$cert" -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -n "$expiry" ]; then
echo "$cert: $expiry"
fi
done | sort -t: -k2
# Check remote certificate expiration
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -enddate
Verify Key/Certificate Match
# Compare modulus (RSA)
openssl x509 -in cert.pem -noout -modulus | openssl md5
openssl rsa -in key.pem -noout -modulus | openssl md5
# Both should output the same hash
# Compare public key (works for all key types)
openssl x509 -in cert.pem -pubkey -noout | openssl md5
openssl pkey -in key.pem -pubout | openssl md5
# Verify CSR matches key
openssl req -in request.csr -noout -modulus | openssl md5
openssl rsa -in key.pem -noout -modulus | openssl md5
Testing SSL/TLS Connections
OpenSSL s_client
# Basic connection test
openssl s_client -connect example.com:443
# With SNI (Server Name Indication)
openssl s_client -connect example.com:443 -servername example.com
# Show certificate chain
openssl s_client -connect example.com:443 -showcerts
# Test specific TLS version
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3
# Test specific cipher
openssl s_client -connect example.com:443 -cipher 'ECDHE-RSA-AES256-GCM-SHA384'
# Verify certificate
openssl s_client -connect example.com:443 -verify_return_error
# Extract remote certificate
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 > remote.pem
# Check OCSP stapling
openssl s_client -connect example.com:443 -status
Test STARTTLS
# SMTP
openssl s_client -connect mail.example.com:25 -starttls smtp
# IMAP
openssl s_client -connect mail.example.com:143 -starttls imap
# POP3
openssl s_client -connect mail.example.com:110 -starttls pop3
# FTP
openssl s_client -connect ftp.example.com:21 -starttls ftp
# LDAP
openssl s_client -connect ldap.example.com:389 -starttls ldap
# PostgreSQL
openssl s_client -connect db.example.com:5432 -starttls postgres
# MySQL
openssl s_client -connect db.example.com:3306 -starttls mysql
Cipher Suite Testing
# List available ciphers
openssl ciphers -v 'ALL:COMPLEMENTOFALL'
# Test TLS 1.3 ciphers
openssl ciphers -v -tls1_3
# Test if server supports specific cipher
openssl s_client -connect example.com:443 -cipher 'AES256-SHA'
# Enumerate supported ciphers (script)
for cipher in $(openssl ciphers 'ALL:eNULL' | tr ':' ' '); do
result=$(openssl s_client -cipher "$cipher" -connect example.com:443 2>&1)
if echo "$result" | grep -q "Cipher is"; then
echo "SUPPORTED: $cipher"
fi
done
ACME / Let’s Encrypt
Certbot Installation
# Debian/Ubuntu
sudo apt install certbot
# RHEL/CentOS
sudo dnf install certbot
# Arch Linux
sudo pacman -S certbot
# With nginx plugin
sudo apt install python3-certbot-nginx
sudo dnf install python3-certbot-nginx
# With Apache plugin
sudo apt install python3-certbot-apache
sudo dnf install python3-certbot-apache
Certificate Operations
# Obtain certificate (standalone)
sudo certbot certonly --standalone -d example.com -d www.example.com
# Obtain certificate (webroot)
sudo certbot certonly --webroot -w /var/www/html -d example.com
# Obtain certificate (nginx)
sudo certbot --nginx -d example.com
# Obtain certificate (Apache)
sudo certbot --apache -d example.com
# Dry run (test without obtaining)
sudo certbot certonly --dry-run --standalone -d example.com
# List certificates
sudo certbot certificates
# Renew all certificates
sudo certbot renew
# Renew specific certificate
sudo certbot renew --cert-name example.com
# Force renewal
sudo certbot renew --force-renewal
# Delete certificate
sudo certbot delete --cert-name example.com
Certificate Locations
# Let's Encrypt certificate paths
/etc/letsencrypt/live/example.com/
├── cert.pem # Server certificate
├── chain.pem # Intermediate CA chain
├── fullchain.pem # cert.pem + chain.pem
├── privkey.pem # Private key
└── README
# Archive (actual files)
/etc/letsencrypt/archive/example.com/
# Renewal configuration
/etc/letsencrypt/renewal/example.com.conf
Automated Renewal
# Certbot systemd timer (usually enabled by default)
sudo systemctl enable --now certbot.timer
sudo systemctl list-timers certbot.timer
# Manual cron entry
# /etc/cron.d/certbot
0 0,12 * * * root certbot renew --quiet
# Test renewal
sudo certbot renew --dry-run
# Pre/post hooks
sudo certbot renew \
--pre-hook "systemctl stop nginx" \
--post-hook "systemctl start nginx"
# Deploy hook
sudo certbot renew \
--deploy-hook "systemctl reload nginx"
Wildcard Certificates
# Wildcard requires DNS challenge
sudo certbot certonly --manual --preferred-challenges dns \
-d example.com -d '*.example.com'
# Using DNS plugin (Cloudflare example)
sudo apt install python3-certbot-dns-cloudflare
cat > ~/.secrets/cloudflare.ini << 'EOF'
dns_cloudflare_api_token = your-api-token
EOF
chmod 600 ~/.secrets/cloudflare.ini
sudo certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials ~/.secrets/cloudflare.ini \
-d example.com -d '*.example.com'
Server Configuration
Nginx SSL Configuration
server {
listen 443 ssl http2;
server_name example.com;
# Certificate and key
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# SSL parameters
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
# Modern configuration (TLS 1.3 only)
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;
# Intermediate configuration (TLS 1.2 + 1.3)
# ssl_protocols TLSv1.2 TLSv1.3;
# ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:...;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
# HSTS
add_header Strict-Transport-Security "max-age=63072000" always;
}
Apache SSL Configuration
<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
# Modern configuration
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:...
SSLHonorCipherOrder off
# OCSP stapling
SSLUseStapling On
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
# HSTS
Header always set Strict-Transport-Security "max-age=63072000"
</VirtualHost>
Troubleshooting
Common Errors
Certificate Chain Incomplete
# Error: unable to verify the first certificate
# Solution: Include intermediate certificates
# Check what's missing
openssl s_client -connect example.com:443 2>&1 | grep -i verify
# Build complete chain
cat server.crt intermediate.crt > fullchain.crt
# Verify chain
openssl verify -CAfile root.crt -untrusted intermediate.crt server.crt
Certificate Hostname Mismatch
# Error: hostname mismatch
# Check certificate CN and SAN
openssl x509 -in cert.pem -noout -subject -ext subjectAltName
# Verify against hostname
openssl s_client -connect 192.168.1.100:443 -servername example.com
Certificate Expired
# Check expiration
openssl x509 -in cert.pem -noout -dates
# Check remote
echo | openssl s_client -connect example.com:443 2>/dev/null | \
openssl x509 -noout -dates
# Monitor with script
DAYS=30
for cert in /etc/letsencrypt/live/*/cert.pem; do
if ! openssl x509 -checkend $((DAYS * 86400)) -noout -in "$cert" 2>/dev/null; then
echo "EXPIRING: $cert"
fi
done
Key/Certificate Mismatch
# Error: key values mismatch
# Verify they match
openssl x509 -in cert.pem -noout -modulus | openssl md5
openssl rsa -in key.pem -noout -modulus | openssl md5
# If different, regenerate CSR with correct key
openssl req -new -key correct.key -out new.csr
Permission Issues
# Private keys should be readable only by owner
chmod 600 /etc/ssl/private/key.pem
chown root:root /etc/ssl/private/key.pem
# Or readable by service group
chown root:ssl-cert /etc/ssl/private/key.pem
chmod 640 /etc/ssl/private/key.pem
# Certificate can be world-readable
chmod 644 /etc/ssl/certs/cert.pem
Debug Techniques
# Verbose SSL connection debug
openssl s_client -connect example.com:443 -debug -msg
# View TLS handshake
openssl s_client -connect example.com:443 -tlsextdebug
# Trace with strace
strace -f -e trace=network openssl s_client -connect example.com:443
# Check certificate with curl
curl -v --cacert ca.crt https://example.com
# Ignore certificate errors (testing only)
curl -k https://example.com
Certificate Monitoring
Expiration Monitoring Script
#!/bin/bash
# check-certs.sh - Monitor certificate expiration
WARN_DAYS=30
CRIT_DAYS=7
CERTS_DIR="/etc/ssl/certs"
check_cert() {
local cert="$1"
local name=$(basename "$cert")
if ! openssl x509 -checkend 0 -noout -in "$cert" 2>/dev/null; then
echo "CRITICAL: $name is EXPIRED"
return 2
fi
if ! openssl x509 -checkend $((CRIT_DAYS * 86400)) -noout -in "$cert" 2>/dev/null; then
echo "CRITICAL: $name expires in less than $CRIT_DAYS days"
return 2
fi
if ! openssl x509 -checkend $((WARN_DAYS * 86400)) -noout -in "$cert" 2>/dev/null; then
echo "WARNING: $name expires in less than $WARN_DAYS days"
return 1
fi
return 0
}
# Check all certificates
for cert in "$CERTS_DIR"/*.pem; do
check_cert "$cert"
done
Remote Certificate Check
#!/bin/bash
# check-remote-certs.sh - Check remote SSL certificates
HOSTS="
example.com:443
mail.example.com:465
api.example.com:443
"
for hostport in $HOSTS; do
host=$(echo "$hostport" | cut -d: -f1)
port=$(echo "$hostport" | cut -d: -f2)
expiry=$(echo | openssl s_client -connect "$hostport" -servername "$host" 2>/dev/null | \
openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -n "$expiry" ]; then
expiry_epoch=$(date -d "$expiry" +%s)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
echo "$hostport: $days_left days ($expiry)"
else
echo "$hostport: FAILED to get certificate"
fi
done
Quick Command Reference
# Generate
openssl genrsa -out key.pem 4096 # RSA key
openssl ecparam -genkey -name secp384r1 -out key.pem # ECDSA key
openssl req -new -key key.pem -out request.csr # CSR
openssl req -x509 -newkey rsa:4096 -out cert.pem -days 365 -nodes # Self-signed
# View
openssl x509 -in cert.pem -text -noout # Full details
openssl x509 -in cert.pem -noout -dates # Validity
openssl x509 -in cert.pem -noout -subject # Subject
openssl req -in request.csr -text -noout # CSR details
openssl rsa -in key.pem -text -noout # Key details
# Verify
openssl verify -CAfile ca.crt cert.pem # Chain
openssl x509 -in cert.pem -noout -modulus | openssl md5 # Match check
# Convert
openssl x509 -in cert.pem -outform DER -out cert.der # PEM → DER
openssl pkcs12 -export -out cert.p12 -inkey key.pem -in cert.pem # → PKCS12
openssl pkcs12 -in cert.p12 -out all.pem -nodes # PKCS12 → PEM
# Test
openssl s_client -connect host:443 -servername host # Connection
openssl s_client -connect host:443 -showcerts # Chain
certbot certificates # Let's Encrypt status