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:

  1. Root CA - Self-signed, stored in system trust stores, issues intermediate CAs

  2. Intermediate CA - Signed by Root CA, issues end-entity certificates

  3. 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 Formats

Format Extension Encoding Use Case

PEM

.pem, .crt, .cer

Base64 ASCII

Most common on Linux

DER

.der, .cer

Binary

Windows, Java

PKCS#12

.p12, .pfx

Binary (contains key + cert)

Windows, browsers

PKCS#7

.p7b, .p7c

ASCII/Binary

Certificate chains (no keys)

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

Arch Linux

# CA bundle location
/etc/ssl/certs/ca-certificates.crt

# Add custom CA
sudo cp my-ca.crt /etc/ca-certificates/trust-source/anchors/
sudo update-ca-trust

# Alternative: trust command
sudo trust anchor 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