Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 1: Cryptography, PKI Structure & Fundamentals

Before You Begin: This chapter builds the conceptual foundation you need before touching a single certificate. By the end, you will understand why cryptography exists, how it works at a practical level, and what happens behind the scenes when two machines establish a secure connection.


1.1 Why Use Cryptography?

Imagine sending a postcard. Anyone who handles it—postal workers, neighbors, strangers—can read it. Now imagine the postcard contains your bank password. That is what unencrypted network traffic looks like.

Every packet traveling across a network can be intercepted, read, modified, or forged. Without cryptography:

ThreatWhat happensReal-world example
EavesdroppingAttacker reads your dataCapturing passwords on public Wi-Fi
TamperingAttacker modifies data in transitInjecting malware into a software download
ImpersonationAttacker pretends to be someone elseFake banking website collecting credentials
RepudiationSender denies having sent a messageDenying a financial transaction

Cryptography solves all four of these problems. It is not optional in modern systems—it is the foundation of every secure communication.


1.2 The Four Pillars of Information Security

Cryptography provides four fundamental guarantees. Every secure system relies on a combination of these:

Confidentiality — “Only you can read this”

Confidentiality ensures that data is readable only by the intended recipient. Even if an attacker intercepts the data, they see only meaningless noise.

How it is implemented:

  • Symmetric encryption (AES-256): Same key encrypts and decrypts. Fast, used for bulk data.
  • Asymmetric encryption (RSA, ECC): Public key encrypts, private key decrypts. Used for key exchange.

What it protects against: Eavesdropping.

Integrity — “This has not been changed”

Integrity guarantees that data has not been altered between sender and receiver. If even a single bit changes, the modification is detected.

How it is implemented:

  • Hash functions (SHA-256): Produce a fixed-size fingerprint of data.
  • HMAC: Hash combined with a secret key for authenticated integrity.
  • Digital signatures: Hash signed with a private key.
 Original:  "Transfer $100 to Bob"  → SHA-256 → a1b2c3d4...
 Tampered:  "Transfer $900 to Bob"  → SHA-256 → f7e8d9c0...  ← DIFFERENT!

What it protects against: Tampering.

Authenticity — “You are who you say you are”

Authenticity proves the identity of the communicating party. When you connect to your bank’s website, you need assurance that it is actually your bank, not an impersonator.

How it is implemented:

  • Digital certificates (X.509): Bind a public key to an identity.
  • Certificate Authorities (CAs): Trusted third parties that verify identities.
  • Digital signatures: Prove that a message was created by the claimed sender.

What it protects against: Impersonation.

Non-Repudiation — “You cannot deny this”

Non-repudiation ensures that the sender cannot deny having sent a message or performed an action. This is the digital equivalent of a handwritten signature on a contract.

How it is implemented:

  • Digital signatures with private keys: Only the key holder can produce the signature.
  • Timestamping: Proves when an action occurred.
  • Audit logs with cryptographic integrity: Tamper-evident records.

What it protects against: Repudiation (denying responsibility).

Summary: The Four Pillars

PillarQuestion it answersImplemented byProtects against
ConfidentialityCan anyone else read this?Encryption (AES, RSA)Eavesdropping
IntegrityHas this been modified?Hashes (SHA-256), HMACTampering
AuthenticityWho sent this?Certificates, signaturesImpersonation
Non-repudiationCan the sender deny it?Digital signaturesRepudiation

1.3 Hash Functions: Digital Fingerprints

A hash function takes an input of any size and produces an output of fixed size. Think of it as a fingerprint for data.

Properties and Examples

1. Deterministic — Same input always produces the same output.

SHA-256("Hello") → 185f8db32271...  (always)
SHA-256("Hello") → 185f8db32271...  (always)

2. One-Way (Pre-image Resistance) — You cannot reverse-engineer the input from the output.

185f8db32271... → ???  (computationally infeasible to find input)

3. Collision Resistant — It is practically impossible to find two different inputs that produce the same output.

SHA-256("input A") → hash1
SHA-256("input B") → hash2
hash1 ≠ hash2  (with overwhelming probability)

4. Avalanche Effect — A tiny change in input produces a completely different output.

SHA-256("Hello World")  → a591a6d40bf420404a011733cfb7b190...
SHA-256("Hello World!") → 7f83b1657ff1fc53b92dc18148a1d65d...
                                     ↑ completely different!

Are Hashes Safe?

Not all hash algorithms are equal. Some have been broken:

AlgorithmOutput SizeStatusWhy
MD5128 bitsBROKENCollisions found in seconds. Never use for security.
SHA-1160 bitsBROKENGoogle demonstrated a practical collision in 2017 (SHAttered).
SHA-256256 bitsSAFENo known practical attacks. Current standard.
SHA-384384 bitsSAFEHigher security margin.
SHA-512512 bitsSAFEMaximum security for SHA-2 family.
SHA-3256+ bitsSAFEDifferent design (Keccak). Future-proof alternative.
BLAKE2256+ bitsSAFEVery fast, used in modern applications.

“Broken” means: An attacker can find two different inputs that produce the same hash (collision). This allows forging documents, certificates, or signatures.

# Verify for yourself — compute hashes on any RHEL system:
echo -n "Hello World" | sha256sum
# a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b57b277d9ad9f146e

echo -n "Hello World" | md5sum
# b10a8db164e0754105b7a99be72e3fe5  ← DO NOT trust this for security!

Real-World Uses of Hashes

Use caseHowExample
Password storageStore hash, not password/etc/shadow on Linux
File integrityCompare hash before/aftersha256sum package.rpm
Digital signaturesSign the hash, not the dataCertificate signing
DeduplicationIdentify identical filesBackup systems
BlockchainChain of hashesBitcoin proof-of-work

1.4 Symmetric vs Asymmetric Cryptography

Symmetric: One Key for Everything

Both sender and receiver share the same secret key. Like a padlock where both parties have a copy of the same key.

PropertyValue
SpeedVery fast (hardware-accelerated AES)
Key size128 or 256 bits
ProblemHow do you share the key securely in the first place?
ExamplesAES-128, AES-256, ChaCha20

Asymmetric: Two Keys, Two Roles

Each party has a key pair: a public key (share freely) and a private key (never share).

PropertyValue
SpeedSlow (1000x slower than symmetric)
Key size2048–4096 bits (RSA) or 256 bits (ECC)
AdvantageNo need to share a secret key beforehand
ExamplesRSA, ECDSA, Ed25519

Why We Need Both: Hybrid Encryption

Asymmetric cryptography solves the key distribution problem, but it is too slow for bulk data. The solution: use asymmetric to exchange a symmetric key, then use symmetric for the actual data.

This is exactly what happens in every HTTPS connection.


1.5 Understanding Key Exchange: The Color Mixing Analogy

Before diving into the real TLS/RSA handshake, let’s build intuition with a visual analogy. This explains Diffie-Hellman key exchange, the mechanism used in modern TLS to establish a shared secret.

The Problem

Alice and Bob want to agree on a shared secret color that Eve (the eavesdropper) cannot figure out, even though Eve can see everything they send each other.

Why Eve Cannot Cheat

Mixing paint is easy to do but impossible to reverse. You cannot separate mixed paint back into its components. In mathematics, this is analogous to:

  • Easy: Multiply two large primes → get a product (mixing)
  • Hard: Factor a large product → find the primes (unmixing)

This is the one-way function that makes cryptography work.

From Colors to Numbers

Color AnalogyCryptographic Equivalent
Public color (Yellow)Public parameters (large prime, generator)
Alice’s secret (Red)Alice’s private key
Bob’s secret (Blue)Bob’s private key
Mixed color sent (Orange/Green)Public key (computed from private)
Final shared secret (Brown)Shared session key
“Cannot unmix paint”Discrete logarithm problem is computationally hard

1.6 The TLS Handshake: How a Secure Connection Really Works

Now let’s see what actually happens when your browser connects to https://bank.com. This combines everything we’ve learned: hashes, asymmetric crypto, symmetric crypto, certificates, and key exchange.

Step-by-Step Walkthrough

Step 1-2 (Hello): Client and server exchange capabilities and random numbers. These randoms add freshness—they ensure every session is unique even between the same parties.

Step 3 (Certificate): The server proves its identity by sending its X.509 certificate containing its public key.

Step 4 (Verification): This is the critical trust step. The client walks the chain of trust:

Each signature is verified using the issuer’s public key. If any link breaks, the handshake fails.

Step 5 (Key Exchange): The client generates 48 bytes of random data (PreMasterSecret), encrypts it with the server’s public RSA key, and sends it. Only the server’s private key can decrypt this—this is the asymmetric magic.

Step 6 (Key Derivation): Both sides independently compute the same session keys using a Pseudo-Random Function (PRF). This is where we transition from slow asymmetric to fast symmetric.

Step 7-9 (Encrypted Communication): From here on, everything is encrypted with AES-256—thousands of times faster than RSA.

Modern TLS 1.3: Simpler and Faster

TLS 1.3 simplified the handshake by removing RSA key exchange (forward secrecy is now mandatory) and reducing round trips:


1.7 PKI Structure: The Trust Architecture

Public Key Infrastructure (PKI) is the system that manages digital certificates and public keys. It answers the question: “How do I know this public key really belongs to bank.com?”

Why a Chain?

Root CAs are extremely valuable—if compromised, every certificate they ever signed becomes untrusted. So Root CAs are:

  • Stored in offline, air-gapped hardware security modules (HSMs)
  • Used only to sign Intermediate CA certificates
  • Valid for 20-30 years

Intermediate CAs do the daily work of issuing certificates. If compromised:

  • Only that Intermediate’s certificates are affected
  • The Root can revoke the Intermediate and create a new one
  • Damage is contained

Revocation: What Happens When Trust Breaks

When a private key is compromised or a certificate should no longer be trusted:

MethodHow it worksTrade-off
CRL (Certificate Revocation List)CA publishes a list of revoked serial numbersCan be stale (updated periodically)
OCSP (Online Certificate Status Protocol)Client asks CA “is this cert still valid?” in real-timeRequires network, privacy concern
OCSP StaplingServer fetches its own OCSP response and attaches it to the handshakeBest of both worlds

1.8 Putting It All Together: A Complete Example

Let’s trace a complete HTTPS connection from start to finish, seeing every concept in action:


1.9 Key Takeaways

Before moving on to RHEL-specific certificate management, make sure you understand:

ConceptOne-sentence summary
HashesOne-way fingerprints that detect any change (use SHA-256+).
Symmetric encryptionSame key encrypts and decrypts—fast but key distribution is hard.
Asymmetric encryptionPublic/private key pairs—solves key distribution but is slow.
Hybrid encryptionUse asymmetric to exchange keys, then symmetric for data (TLS does this).
Digital signaturesHash + private key = proof of identity and integrity.
CertificatesBind a public key to an identity, signed by a trusted CA.
PKIThe trust architecture: Root CA → Intermediate CA → End-entity certificate.
TLS handshakeAuthenticates server, exchanges keys, then encrypts everything.
Forward secrecyUse ephemeral keys (ECDHE) so past sessions stay safe even if keys leak.

Chapter Navigation

Chapter 2: Introduction to Certificates on RHEL

Welcome! This tutorial will take you from knowing nothing about digital certificates to confidently troubleshooting certificate issues on Red Hat Enterprise Linux systems.


2.1 Why This Tutorial?

You’re a RHEL administrator. One day, something breaks:

  • Apache refuses to start: SSL_CTX_use_certificate:ca md too weak
  • LDAP connections fail: TLS: hostname does not match CN
  • certmonger shows: CA_UNREACHABLE
  • curl returns: SSL certificate problem: unable to get local issuer certificate

Sound familiar? These are certificate issues, and they’re everywhere in modern Linux systems.

This tutorial teaches you to:

  • ✅ Understand what certificates are (RHEL perspective)
  • ✅ Configure certificates for common RHEL services
  • Troubleshoot certificate problems (primary goal!)
  • ✅ Automate certificate lifecycle with RHEL tools
  • ✅ Handle RHEL version differences (7, 8, 9, 10)
  • ✅ Pass audits (FIPS, STIG, compliance)

2.2 Who Is This For?

Primary Audience:

  • RHEL administrators and engineers
  • Support engineers troubleshooting certificate issues
  • Anyone managing RHEL systems with HTTPS, LDAPS, or TLS

Prerequisites:

  • Basic Linux command line knowledge
  • Access to RHEL systems (7, 8, 9, or 10)
  • No prior certificate knowledge needed!

2.3 What Are Certificates? (In 60 Seconds)

Imagine you visit https://example.com. How does your browser know it’s really talking to example.com and not an imposter?

Answer: Digital certificates.

A certificate is like a digital ID card that:

  1. Proves identity (“I am example.com”)
  2. Enables encryption (secure communication)
  3. Is signed by a trusted authority (like a CA)

On RHEL Systems

Certificates are used everywhere:

  • Web servers (Apache, NGINX) → HTTPS
  • Directory services (OpenLDAP, FreeIPA) → LDAPS
  • Mail servers (Postfix, Dovecot) → SMTPS/IMAPS
  • Databases (PostgreSQL, MySQL) → TLS connections
  • APIs and services (REST, microservices) → mTLS
  • VPN tunnels → Secure connections
  • Container registries → Secure images

Bottom line: If it’s networked and secure on RHEL, it probably uses certificates.


2.4 Your First Certificate Inspection

Let’s get hands-on immediately. On any RHEL system, run:

# Check a web server's TLS certificate (HTTPS, port 443)
echo | openssl s_client -connect access.redhat.com:443 2>/dev/null | openssl x509 -noout -text | head -20

# Alternative: inspect mail STARTTLS on port 25 (-starttls smtp matches SMTP, not SSH)
echo | openssl s_client -connect localhost:25 -starttls smtp 2>/dev/null | openssl x509 -noout -text | head -20

You’ll see output like:

Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            0a:5d:d2:48:fc:4e:2f:e2:99:81:09:74:2d:4c:d5:69
        Signature Algorithm: ecdsa-with-SHA384
        Issuer: C=US, O=DigiCert Inc, CN=DigiCert Global G3 TLS ECC SHA384 2020 CA1
        Validity
            Not Before: Oct 30 00:00:00 2025 GMT
            Not After : Oct 27 23:59:59 2026 GMT
        Subject: C=US, ST=North Carolina, L=Raleigh, O=Red Hat, Inc., CN=access.redhat.com
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:fc:08:bf:d2:d8:63:0c:84:a4:c8:dd:04:9c:8c:
                    99:4f:cb:93:31:7f:9e:64:27:ea:3d:a7:18:fd:3e:
                    4c:c2:58:8b:cb:f2:5c:6e:95:bf:f3:97:ba:b8:2b:
                    49:c6:51:30:f4:71:88:e3:fa:d4:f1:73:74:1d:e3:
                    2b:49:bc:9e:6e

What you’re seeing:

  • Issuer: Who signed this certificate
  • Subject: Who this certificate belongs to
  • Validity: When it’s valid (Not Before / Not After dates)
  • Signature Algorithm: How it’s secured (for example, ECDSA with SHA-384)

🎉 Congratulations! You just inspected your first certificate.


2.5 How Certificates Work (RHEL Context)

The Three Key Components

  1. Certificate (.crt, .pem)

    • Public information: “I am server.example.com”
    • Contains the public key
    • Stored in /etc/pki/tls/certs/ on RHEL
  2. Private Key (.key, .pem)

    • Secret! Never share this
    • Used to prove you own the certificate
    • Stored in /etc/pki/tls/private/ on RHEL (mode 600!)
  3. Certificate Authority (CA)

    • Issues and signs certificates
    • Could be public (Let’s Encrypt, DigiCert)
    • Or internal (FreeIPA, corporate CA)
    • Trusted CAs stored in /etc/pki/ca-trust/ on RHEL

The Trust Chain

Root CA (trusted by RHEL system)
  └─ Intermediate CA
      └─ Server Certificate (your web server)

When someone connects to your RHEL server:

  1. Server sends its certificate
  2. Client verifies signature chain back to a trusted root CA
  3. If chain is valid → connection proceeds
  4. If chain breaks → error (and you get the support call!)

2.6 RHEL’s Certificate Architecture

Key Directories

/etc/pki/
├── ca-trust/
│   ├── source/anchors/      ← Put custom CA certs here
│   └── extracted/           ← System-wide trust store
│       ├── pem/             ← PEM format CAs
│       ├── openssl/         ← OpenSSL trust
│       └── java/            ← Java trust (cacerts)
├── tls/
│   ├── certs/               ← Server certificates
│   ├── private/             ← Private keys (mode 700!)
│   └── cert.pem             ← Default certificate symlink
└── nssdb/                   ← NSS database (Firefox, etc.)

Key Tools

# OpenSSL - Swiss army knife of certificates
openssl version  # Check your version

# NSS Tools - For NSS databases
certutil -L -d /etc/pki/nssdb

# Trust Management - Add/remove CAs
update-ca-trust  # RHEL's trust store updater

# Certificate Manager - Automatic renewal (RHEL 7+)
getcert list  # Show tracked certificates

# Crypto-Policies - System-wide security (RHEL 8+)
update-crypto-policies --show  # Check current policy

2.7 A Day in the Life: Certificate Scenarios

Scenario 1: Adding a Custom CA

# You have a corporate CA that signed your internal servers
sudo cp corporate-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust extract

# Now RHEL trusts certificates signed by your corporate CA!

Scenario 2: Setting Up Apache HTTPS

# Install Apache with SSL/TLS
sudo dnf install httpd mod_ssl

# Generate a private key
sudo openssl genpkey -algorithm RSA -out /etc/pki/tls/private/server.key \
  -pkeyopt rsa_keygen_bits:2048

# Generate a certificate signing request (CSR)
sudo openssl req -new -key /etc/pki/tls/private/server.key \
  -out /tmp/server.csr \
  -subj "/CN=web.example.com"

# Send CSR to CA, get certificate back, install it
sudo cp server.crt /etc/pki/tls/certs/

# Configure Apache, restart
sudo systemctl restart httpd

Scenario 3: Troubleshooting an Expired Certificate

# Service fails with: "certificate has expired"
# Check certificate expiration
sudo openssl x509 -in /etc/pki/tls/certs/server.crt -noout -dates

# Output shows:
# notAfter=Jan 15 23:59:59 2024 GMT  ← Oops, expired!

# Renew certificate, replace file, restart service

2.8 RHEL Version Differences (Preview)

Certificate management has evolved significantly across RHEL versions:

RHEL VersionKey FeatureTroubleshooting Focus
RHEL 7Traditional approachManual configuration, legacy TLS issues
RHEL 8Crypto-policiesPolicy conflicts, certmonger integration
RHEL 9OpenSSL 3.xProvider issues, stricter validation
RHEL 10Hardened defaultsModern-only, enhanced tooling

Don’t worry! Chapter 8 covers these version differences in detail.


2.9 Common Certificate Problems (Preview)

You’ll learn to troubleshoot:

Configuration Issues:

  • Certificate/key mismatch
  • Wrong file permissions
  • Incorrect paths in config files

Trust Issues:

  • Self-signed certificates rejected
  • Unknown CA errors
  • Chain validation failures

Expiration Issues:

  • Expired certificates
  • Clock skew problems
  • Renewal failures

Version Issues:

  • TLS version mismatches
  • Cipher suite problems
  • Crypto-policy conflicts (RHEL 8+)
  • OpenSSL 3.x compatibility (RHEL 9+)

Service-Specific:

  • Apache: SSLCertificateFile errors
  • NGINX: ssl_certificate problems
  • Postfix: TLS handshake failures
  • LDAP: TLS: hostname does not match

2.10 Learning Path Overview

This tutorial is organized for RHEL administrators:

Part 1: Foundations (Chapters 1-7)

Start here. Learn certificate basics in RHEL context.

Part 2: Version-Specific (Chapters 8-13)

Deep dive into RHEL 7, 8, 9, 10 differences.

Part 3: Services (Chapters 14-21)

Configure certificates for Apache, NGINX, Postfix, LDAP, etc.

Part 4: Automation (Chapters 22-26)

Master certmonger, crypto-policies, Let’s Encrypt, Ansible.

Part 5: Troubleshooting (Chapters 27-33) ⭐

This is where you become an expert! Systematic troubleshooting, common errors, emergency procedures.

Part 6: Migration (Chapters 34-37)

RHEL version upgrades and certificate migration.

Part 7: Security (Chapters 38-41)

FIPS mode, compliance, hardening, auditing.

Appendices

Optional advanced topics (Kubernetes, Vault, Zero Trust, etc.)


2.11 How to Use This Tutorial

For New Users

📖 Read chapters in order. Each builds on previous knowledge.

For Experienced Users

🎯 Jump to troubleshooting (Part 5) or specific services (Part 3).

For Support Engineers

🚨 Start with Chapter 27 (Troubleshooting Methodology), then dive into specifics.

Hands-On Labs

Every chapter includes practical examples. You’ll need:

  • A RHEL system (VM or container is fine)
  • Root or sudo access
  • Internet connectivity (for package installs)

2.12 Key Concepts to Master

By the end of this tutorial, you’ll understand:

  • What certificates are and why RHEL uses them
  • How trust works on RHEL systems
  • Where certificates live (/etc/pki/)
  • Which tools to use (openssl, certutil, certmonger)
  • Version differences (RHEL 7 vs 8 vs 9 vs 10)
  • How to troubleshoot any certificate issue
  • How to automate certificate lifecycle
  • How to secure systems (FIPS, compliance)

2.13 Real-World Impact

Certificate issues cause:

  • ❌ Service outages (expired certs)
  • ❌ Security vulnerabilities (weak ciphers)
  • ❌ Failed migrations (RHEL upgrades)
  • ❌ Compliance failures (audit rejections)
  • ❌ Lost productivity (troubleshooting time)

After this tutorial:

  • ✅ Prevent issues before they happen
  • ✅ Troubleshoot problems in minutes, not hours
  • ✅ Automate certificate management
  • ✅ Pass security audits
  • ✅ Confidently migrate RHEL versions

2.14 Your First Exercise

Let’s verify your RHEL system is ready:

# Check RHEL version
cat /etc/redhat-release

# Check OpenSSL
openssl version

# Check if certmonger is installed
rpm -q certmonger

# Check if you can use sudo
sudo whoami

# Check internet connectivity (for package installs)
ping -c 3 access.redhat.com

# List current trusted CAs (sample)
trust list | head -20

✅ If all commands work, you’re ready to proceed!


2.15 Let’s Begin!

You now understand:

  • What certificates are
  • Why they matter on RHEL
  • Where they live on the filesystem
  • Which tools you’ll use
  • What you’ll learn in this tutorial

Ready to dive deeper?


Quick Reference

┌────────────────────────────────────────────────────────────┐
│ CERTIFICATE QUICK START (RHEL)                             │
├────────────────────────────────────────────────────────────┤
│ View cert:     openssl x509 -in cert.crt -noout -text      │
│ Check expiry:  openssl x509 -in cert.crt -noout -dates     │
│ Add CA:        cp ca.crt /etc/pki/ca-trust/source/anchors/ │
│                sudo update-ca-trust                        │
│ List tracked:  getcert list                                │
│ Check policy:  update-crypto-policies --show  (RHEL 8+)    │
└────────────────────────────────────────────────────────────┘

Cert location:  /etc/pki/tls/certs/
Key location:   /etc/pki/tls/private/  (mode 600!)
CA trust:       /etc/pki/ca-trust/

🧪 Hands-On Lab

Lab 01: Environment Setup

Validate your RHEL environment and install essential certificate management tools

  • 📁 Location: labs/en_US/01-environment-setup/
  • ⏱️ Time: 15-20 minutes
  • 🎯 Level: Beginner

Chapter Navigation

Chapter 3: RHEL Certificate Tools Overview

Learning Objective: Get familiar with the essential tools for managing certificates on RHEL so you know which tool to use for each task.


3.1 Your Certificate Toolkit

When working with certificates on RHEL, you’ll use these core tools:

ToolPrimary UseRHEL VersionsWhen to Use
opensslCertificate operations, testingAllGenerate keys/CSRs, inspect certs, test connections
certutilNSS database managementAllFirefox/Mozilla-style cert DBs
update-ca-trustTrust store managementAllAdd/remove trusted CAs
certmongerAutomatic renewalAllTrack and auto-renew certificates
crypto-policiesSystem-wide securityRHEL 8+Control TLS versions and ciphers
getcertcertmonger CLIAllRequest and manage tracked certs
trustP11-kit trust managementAll (enhanced RHEL 8+)Advanced trust operations

3.2 OpenSSL - The Swiss Army Knife

Available: All RHEL versions Package: openssl

Version Differences

# Check your version
openssl version

# RHEL 7: OpenSSL 1.0.2k-26
# RHEL 8: OpenSSL 1.1.1k-14
# RHEL 9: OpenSSL 3.5.5-2
# RHEL 10: OpenSSL 3.5.5-2

Common Uses

#============================================#
# INSPECT CERTIFICATES
#============================================#

# View certificate details
openssl x509 -in cert.crt -noout -text

# Check expiration
openssl x509 -in cert.crt -noout -dates
openssl x509 -in cert.crt -noout -checkend 86400  # Check if expires in 24h

# View certificate subject
openssl x509 -in cert.crt -noout -subject -issuer


#============================================#
# GENERATE KEYS
#============================================#

# RHEL 7 style (still works on all versions)
openssl genrsa -out server.key 2048

# RHEL 8+ modern style (recommended)
openssl genpkey -algorithm RSA -out server.key -pkeyopt rsa_keygen_bits:2048

# RHEL 9+ EC key (elliptic curve)
openssl genpkey -algorithm EC -out ec.key -pkeyopt ec_paramgen_curve:P-256


#============================================#
# CREATE CSR (Certificate Signing Request)
#============================================#

# Basic CSR
openssl req -new -key server.key -out server.csr \
  -subj "/C=US/ST=State/L=City/O=Organization/CN=server.example.com"

# CSR with SANs (required for modern browsers!)
openssl req -new -key server.key -out server.csr \
  -subj "/CN=server.example.com" \
  -addext "subjectAltName=DNS:server.example.com,DNS:www.example.com"


#============================================#
# TEST CONNECTIONS
#============================================#

# Test HTTPS
openssl s_client -connect server.example.com:443 -servername server.example.com

# Test specific TLS version
openssl s_client -connect server.example.com:443 -tls1_2
openssl s_client -connect server.example.com:443 -tls1_3

# Test LDAPS
openssl s_client -connect ldap.example.com:636

# Test SMTP with STARTTLS
openssl s_client -connect mail.example.com:25 -starttls smtp

Version-Specific Differences

RHEL 7 (OpenSSL 1.0.2k):

  • ✅ Stable and well-tested
  • ❌ No TLS 1.3 support
  • ❌ Older command syntax

RHEL 8 (OpenSSL 1.1.1k):

  • ✅ TLS 1.3 support
  • ✅ Modern command syntax
  • ✅ Better defaults

RHEL 9/10 (OpenSSL 3.5.5):

  • ✅ Provider architecture
  • ✅ Enhanced FIPS support
  • ⚠️ API changes (affects custom apps)
  • ⚠️ Legacy algorithms require -provider legacy

3.3 certutil - NSS Database Tool

Available: All RHEL versions Package: nss-tools

Used for Mozilla/Firefox-style certificate databases.

Common Uses

#============================================#
# MANAGE NSS DATABASE
#============================================#

# Create new database
certutil -N -d /etc/pki/nssdb

# List certificates
certutil -L -d /etc/pki/nssdb

# Add CA certificate
certutil -A -n "My CA" -t "CT,C,C" -d /etc/pki/nssdb -i ca.crt

# Delete certificate
certutil -D -n "Certificate Name" -d /etc/pki/nssdb

# Export certificate
certutil -L -n "Certificate Name" -d /etc/pki/nssdb -a > exported.crt

When to Use certutil

  • Managing Firefox/Thunderbird certificates
  • Working with applications that use NSS (many Red Hat services)
  • When you see .db files in /etc/pki/nssdb/

3.4 update-ca-trust - Trust Store Management

Available: All RHEL versions Package: ca-certificates (installed by default)

Manages which Certificate Authorities (CAs) your system trusts.

How It Works

Your Custom CAs
  ↓
/etc/pki/ca-trust/source/anchors/
  ↓
update-ca-trust extract
  ↓
/etc/pki/ca-trust/extracted/
  ├── pem/tls-ca-bundle.pem       (OpenSSL/Python/Ruby)
  ├── openssl/ca-bundle.trust.crt (OpenSSL specific)
  └── java/cacerts                (Java applications)

Common Uses

#============================================#
# ADD CUSTOM CA
#============================================#

# Step 1: Copy CA certificate
sudo cp corporate-ca.crt /etc/pki/ca-trust/source/anchors/

# Step 2: Update trust store
sudo update-ca-trust extract

# That's it! Now all applications trust this CA


#============================================#
# REMOVE/BLACKLIST CA (RHEL 8+)
#============================================#

# Blacklist a compromised CA
sudo cp compromised-ca.crt /etc/pki/ca-trust/source/blacklist/
sudo update-ca-trust extract


#============================================#
# VERIFY TRUST
#============================================#

# Check if certificate is trusted
openssl verify /path/to/cert.crt

# List all trusted CAs
trust list | grep "certificate-authority"

# Search for specific CA
trust list | grep -i "Let's Encrypt"

Key Directories

/etc/pki/ca-trust/
├── source/
│   ├── anchors/          ← Add your trusted CAs here
│   └── blacklist/        ← Blacklist CAs (RHEL 8+)
└── extracted/
    ├── pem/              ← Used by most apps
    ├── openssl/          ← OpenSSL-specific
    └── java/             ← Java applications

3.5 certmonger - Automatic Certificate Renewal

Available: All RHEL versions Package: certmonger

The “set it and forget it” tool for certificates.

What It Does

certmonger:

  • Tracks certificate expiration dates
  • Automatically renews before expiry
  • Works with multiple CA workflows (IPA, local CA, external helpers)
  • Runs post-renewal commands (e.g., restart services)

Basic Workflow

#============================================#
# INSTALLATION
#============================================#

sudo dnf install certmonger  # RHEL 8/9/10; use yum on RHEL 7
sudo systemctl enable --now certmonger


#============================================#
# REQUEST CERTIFICATE
#============================================#

# From FreeIPA
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/web.crt \
  -k /etc/pki/tls/private/web.key \
  -D web.example.com \
  -K host/web.example.com@REALM

# Self-signed (for testing)
sudo getcert request \
  -f /etc/pki/tls/certs/test.crt \
  -k /etc/pki/tls/private/test.key


#============================================#
# MONITOR CERTIFICATES
#============================================#

# List all tracked certificates
sudo getcert list

# Check specific certificate
sudo getcert list -f /etc/pki/tls/certs/web.crt

# Watch for renewal
sudo journalctl -u certmonger -f

Key Features by Version

RHEL 7:

  • Basic tracking and renewal
  • IPA integration
  • Manual configuration

RHEL 8:

  • Enhanced IPA integration
  • Better error reporting
  • Post-save commands

RHEL 9:

  • Improved monitoring
  • Better status reporting
  • Same strong fit for IPA and tracked renewals
# RHEL 9 - Native FreeIPA integration
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/internal.crt \
  -k /etc/pki/tls/private/internal.key \
  -K HTTP/$(hostname -f)@REALM \
  -D $(hostname -f) \
  -C "systemctl reload httpd"

3.6 crypto-policies - System-Wide Security (RHEL 8+)

Available: RHEL 8, 9, 10 only Package: crypto-policies (installed by default)

GAME CHANGER: Control TLS versions, ciphers, and key sizes system-wide!

The Big Idea

Instead of configuring every application individually:

❌ OLD WAY (RHEL 7):
- Configure Apache SSL ciphers
- Configure NGINX SSL ciphers
- Configure Postfix TLS settings
- Configure OpenLDAP TLS settings
- Configure every application...

✅ NEW WAY (RHEL 8+):
- Set ONE system policy
- All applications follow it automatically!

Available Policies

# Check current policy
update-crypto-policies --show

# Policies:
# DEFAULT  - Balanced security (TLS 1.2+, RSA 2048+)
# LEGACY   - Compatibility mode (allows TLS 1.0/1.1)
# FUTURE   - Stricter security (TLS 1.2+, RSA 3072+)
# FIPS     - Federal compliance mode

Policy Comparison

FeatureLEGACYDEFAULTFUTUREFIPS
TLS 1.0/1.1✅ Allowed❌ Blocked❌ Blocked❌ Blocked
TLS 1.2✅ Yes✅ Yes✅ Yes✅ Yes
TLS 1.3✅ Yes✅ Yes✅ Yes✅ Yes
Min RSA1024 bits2048 bits3072 bits2048 bits
SHA-1 sigs⚠️ Allowed❌ Blocked❌ Blocked❌ Blocked
3DES cipher⚠️ Allowed❌ Blocked❌ Blocked❌ Blocked

Common Uses

#============================================#
# CHANGE POLICY
#============================================#

# Set FUTURE policy (stricter)
sudo update-crypto-policies --set FUTURE
# Reboot or restart services

# Temporarily use LEGACY (for old systems)
sudo update-crypto-policies --set LEGACY
# Note: LEGACY should be temporary!


#============================================#
# CUSTOM POLICIES (RHEL 9+)
#============================================#

# Subpolicies - modify existing policy
sudo update-crypto-policies --set DEFAULT:NO-SHA1
sudo update-crypto-policies --set FUTURE:AD-SUPPORT


#============================================#
# TROUBLESHOOT POLICY ISSUES
#============================================#

# If service fails after policy change:
# 1. Check current policy
update-crypto-policies --show

# 2. Check application config
cat /etc/crypto-policies/back-ends/opensslcnf.config

# 3. Test with LEGACY temporarily
sudo update-crypto-policies --set LEGACY
sudo systemctl restart <service>

What crypto-policies Controls

Automatically configures:

  • OpenSSL
  • GnuTLS
  • NSS
  • OpenJDK/Java
  • BIND
  • Kerberos
  • OpenSSH
  • And more!

Bottom line: Change one setting, update system-wide security. Brilliant!


3.7 Tool Selection Guide

“Which tool should I use?”

┌─────────────────────────────────────────────────────────────┐
│ CERTIFICATE TOOL DECISION TREE                              │
└─────────────────────────────────────────────────────────────┘

I need to...
│
├─ Inspect a certificate
│  └─ Use: openssl x509 -in cert.crt -noout -text
│
├─ Generate a key/CSR
│  └─ Use: openssl genpkey / openssl req
│
├─ Test a TLS connection
│  └─ Use: openssl s_client -connect host:port
│
├─ Add a trusted CA system-wide
│  └─ Use: copy to /etc/pki/ca-trust/source/anchors/
│          then: update-ca-trust
│
├─ Auto-renew certificates
│  └─ Use: certmonger (getcert/ipa-getcert)
│
├─ Change system TLS policy (RHEL 8+)
│  └─ Use: update-crypto-policies --set <POLICY>
│
├─ Work with Firefox/NSS databases
│  └─ Use: certutil
│
└─ Troubleshoot certificate issues
   └─ Use: Chapter 27 methodology!

3.8 Tool Availability Matrix

ToolRHEL 7RHEL 8RHEL 9RHEL 10Notes
openssl1.0.2k1.1.1k3.5.53.5.5Core tool
certutilNSS tool
update-ca-trust✅ Enhanced✅ Enhanced✅ EnhancedTrust mgmt
certmonger✅ Enhanced✅ ACME✅ ACMEAuto-renewal
crypto-policies✅ Subpolicies✅ EnhancedSystem policy
getcertcertmonger CLI
trust✅ Basicp11-kit tool

3.9 Installation Check

Verify you have the essential tools:

#============================================#
# CHECK INSTALLED TOOLS
#============================================#

# OpenSSL (should be installed by default)
openssl version

# NSS tools
rpm -q nss-tools || echo "Install with: sudo dnf install nss-tools"

# certmonger
rpm -q certmonger || echo "Install with: sudo dnf install certmonger (RHEL 8+); sudo yum install certmonger (RHEL 7)"

# Check crypto-policies (RHEL 8+ only)
which update-crypto-policies &>/dev/null && \
  echo "Crypto-policies available: $(update-crypto-policies --show)" || \
  echo "Crypto-policies not available (RHEL 7 or earlier)"

3.10 Quick Reference Commands

# === OpenSSL ===
openssl version                          # Check version
openssl x509 -in cert.crt -noout -text   # Inspect certificate
openssl s_client -connect host:443       # Test HTTPS

# === Trust Store ===
sudo cp ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust                     # Add trusted CA

# === certmonger ===
sudo getcert list                        # List tracked certs
sudo getcert list -f /path/to/cert.crt   # Check specific cert
sudo journalctl -u certmonger -f         # Watch logs

# === Crypto-Policies (RHEL 8+) ===
update-crypto-policies --show            # Current policy
sudo update-crypto-policies --set <POL>  # Change policy

# === NSS ===
certutil -L -d /etc/pki/nssdb            # List NSS certs

3.11 What’s Next?

Now that you know the tools, you’ll learn:

  • Chapter 4: Basic cryptography concepts
  • Chapter 5: Understanding X.509 certificates
  • Chapter 6: RHEL trust store deep dive
  • Chapter 22: certmonger mastery (detailed)
  • Chapter 23: Crypto-policies deep dive (detailed)

Quick Reference Card

┌──────────────────────────────────────────────────────────┐
│ RHEL CERTIFICATE TOOLS CHEAT SHEET                       │
├──────────────────────────────────────────────────────────┤
│ Inspect:     openssl x509 -in cert.crt -noout -text      │
│ Test:        openssl s_client -connect host:443          │
│ Add CA:      cp ca.crt /etc/pki/ca-trust/source/anchors/ │
│              sudo update-ca-trust                        │
│ Auto-renew:  sudo getcert list                           │
│ Policy:      update-crypto-policies --show  (RHEL 8+)    │
│ NSS:         certutil -L -d /etc/pki/nssdb               │
└──────────────────────────────────────────────────────────┘

Chapter Navigation

Chapter 4: Basic Cryptography for RHEL Admins

Practical Focus: Learn the cryptography concepts you need to manage certificates on RHEL - no PhD required!

4.1 Symmetric vs Asymmetric

Symmetric crypto (e.g., AES) relies on a single shared secret. In contrast, asymmetric crypto provides two complementary keys:

  • Public key — share freely, used for encryption or signature verification.
  • Private key — keep secret, used for decryption or signing.

4.2 RSA in a Nutshell

  1. Select two large primes p and q.
  2. Compute modulus n = p × q.
  3. Derive public exponent e and private exponent d such that e × d ≡ 1 (mod φ(n)).
  4. The pair (n, e) is public; (n, d) is private.

Strength derives from the difficulty of factoring n.

4.3 Elliptic-Curve Cryptography (ECC)

ECC offers comparable security with much smaller key sizes by operating over points on elliptic curves. Popular curves include secp256r1 (P-256) and Curve25519.

Algorithm128-bit security key size
RSA3072 bits
ECC256 bits

4.4 Key Generation Lab (OpenSSL)

# Generate 3072-bit RSA private key
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out rsa.key.pem

# Extract public key
openssl pkey -in rsa.key.pem -pubout -out rsa.pub.pem

# Generate P-256 EC key-pair
openssl ecparam -genkey -name prime256v1 -out ec.key.pem
openssl pkey -in ec.key.pem -pubout -out ec.pub.pem

We will reuse these keys in later chapters to create certificates.

4.5 Hybrid Encryption

In TLS a symmetric session key is exchanged using asymmetric crypto (RSA or ECDHE). This yields the best of both worlds: efficiency and secure key exchange.


4.6 RHEL-Specific Considerations

Key Generation on RHEL by Version

RHEL 7 (OpenSSL 1.0.2k):

# Old style (still works on all versions)
openssl genrsa -out server.key 2048

# Extract public key
openssl rsa -in server.key -pubout -out server.pub

RHEL 8+ (OpenSSL 1.1.1k / 3.5.5):

# Modern style (recommended)
openssl genpkey -algorithm RSA -out server.key -pkeyopt rsa_keygen_bits:2048

# Extract public key
openssl pkey -in server.key -pubout -out server.pub

Minimum Key Sizes by RHEL Version

RHEL VersionMinimum RSAMinimum ECCEnforced By
RHEL 7None (weak allowed)NoneManual config
RHEL 82048 bitsP-256crypto-policy DEFAULT
RHEL 92048 bitsP-256crypto-policy DEFAULT
RHEL 102048 bitsP-256crypto-policy DEFAULT

Recommendation: Always use RSA 2048+ or ECC P-256+ for compatibility!

Testing on RHEL

# Generate test key pair (RHEL 8+)
openssl genpkey -algorithm RSA -out test.key -pkeyopt rsa_keygen_bits:2048

# Verify key
openssl pkey -in test.key -text -noout

# Create test data
echo "Hello RHEL" > message.txt

# Sign with private key
openssl dgst -sha256 -sign test.key -out message.sig message.txt

# Verify with public key
openssl dgst -sha256 -verify test.pub -signature message.sig message.txt
# Verified OK

Quick Reference

┌─────────────────────────────────────────────────────────┐
│ CRYPTOGRAPHY FOR RHEL ADMINS                            │
├─────────────────────────────────────────────────────────┤
│ Asymmetric:   Public key (share) + Private key (secret) │
│ Algorithms:   RSA, ECC (Elliptic Curve)                 │
│                                                         │
│ RSA Sizes:    2048 bits (minimum on RHEL 8+)            │
│               4096 bits (recommended)                   │
│                                                         │
│ ECC Curves:   P-256 (secp256r1) - minimum               │
│               P-384 (secp384r1) - recommended           │
│                                                         │
│ RHEL 7:       openssl genrsa -out key 2048              │
│ RHEL 8/9/10:  openssl genpkey -algorithm RSA -out key   │
│                                                         │
│ Use case:     TLS/SSL certificates                      │
│ Security:     Private key MUST be protected (chmod 600) │
└─────────────────────────────────────────────────────────┘

🧪 Hands-On Lab

Lab 02: Key Generation

Practice generating cryptographic key pairs in this hands-on exercise:

  • Generate RSA keys (2048-bit and 4096-bit)

  • Generate ECC keys (P-256 and P-384)

  • Extract public keys from private keys

  • Compare key sizes and performance

  • 📁 Location: labs/en_US/02-key-generation/

  • ⏱️ Time: 20-25 minutes

  • 🎯 Level: Beginner


Chapter Navigation

Chapter 5: X.509 Certificates on RHEL

Standard Format: X.509 is the certificate standard used everywhere on RHEL. Learn its structure and how to work with it on Red Hat systems.

5.1 Origins of the Standard

X.509 emerged from the X.500 directory project (ITU-T, 1988) to define a standard identity certificate—a document that binds a public key to a subject name, signed by a trusted authority.

5.2 Certificate Anatomy

FieldPurpose
VersionUsually v3 (adds extensions)
Serial NumberUnique per CA
Signature Algorithme.g., sha256WithRSAEncryption
IssuerDistinguished Name (DN) of CA
ValidityNot Before & Not After dates
SubjectDN of entity (CN, O, C…)
Subject Public Key InfoAlgorithm + Key
ExtensionsKey Usage, SAN, CRL DP, etc.
SignatureCA’s digital signature

5.3 Common Extensions

  • Subject Alternative Name (SAN) — Hosts/IPs bound to cert.
  • Key Usage / Extended Key Usage — Permitted operations (TLS server, code signing…).
  • Basic Constraints — Indicates if cert can sign others (CA:TRUE).

5.4 Viewing a Certificate

openssl x509 -in server.crt -noout -text

Observe each section matches the table above.

5.5 PEM vs DER Encodings

  • PEM — Base64 + -----BEGIN CERTIFICATE----- headers (most common on RHEL).
  • DER — Binary ASN.1, useful for embedded devices.

5.6 X.509 on RHEL Systems

Certificate Locations on RHEL

# Standard RHEL certificate locations
/etc/pki/tls/certs/          # Server certificates (public)
/etc/pki/tls/private/        # Private keys (mode 600!)
/etc/pki/ca-trust/           # Trusted CA certificates
/etc/pki/nssdb/              # NSS database (Firefox, etc.)

# Service-specific locations
/etc/httpd/conf/ssl.crt/     # Apache (alternative)
/etc/nginx/certs/            # NGINX (custom)
/var/lib/pgsql/data/         # PostgreSQL
/etc/openldap/certs/         # OpenLDAP

Viewing Certificates on RHEL

# View full certificate details
openssl x509 -in /etc/pki/tls/certs/server.crt -noout -text

# Quick checks (RHEL sysadmin focus)
openssl x509 -in server.crt -noout -subject             # Who is it for?
openssl x509 -in server.crt -noout -issuer              # Who signed it?
openssl x509 -in server.crt -noout -dates               # When is it valid?
openssl x509 -in server.crt -noout -ext subjectAltName  # SANs (critical!)

# Check if expired
openssl x509 -in server.crt -noout -checkend 0
# Exit 0 = valid, Exit 1 = expired

RHEL Version Differences for X.509

RHEL VersionOpenSSLValidation StrictnessKey Changes
RHEL 71.0.2kStandardSANs recommended
RHEL 81.1.1kStricterSANs strongly recommended
RHEL 93.5.5Very strictSANs required, SHA-1 blocked
RHEL 103.5.5Very strictSame as RHEL 9

Key Point: Modern browsers and RHEL 9+ require SANs (Subject Alternative Names)!

Creating X.509 Certificates on RHEL

# Complete workflow on RHEL

# Step 1: Generate private key
openssl genpkey -algorithm RSA -out server.key -pkeyopt rsa_keygen_bits:2048

# Step 2: Create CSR (Certificate Signing Request)
openssl req -new -key server.key -out server.csr \
  -subj "/C=US/ST=State/O=Company/CN=server.example.com" \
  -addext "subjectAltName=DNS:server.example.com,DNS:www.example.com"

# Step 3: Self-signed (testing only!)
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

# Step 4: View your X.509 certificate
openssl x509 -in server.crt -noout -text

# Step 5: Install on RHEL
sudo cp server.crt /etc/pki/tls/certs/
sudo cp server.key /etc/pki/tls/private/
sudo chmod 600 /etc/pki/tls/private/server.key

Quick Reference

┌─────────────────────────────────────────────────────────────────┐
│ X.509 CERTIFICATES ON RHEL                                      │
├─────────────────────────────────────────────────────────────────┤
│ Standard:  X.509 v3 (with extensions)                           │
│ Encoding:  PEM (Base64, human-readable)                         │
│                                                                 │
│ View:      openssl x509 -in cert.crt -noout -text               │
│ Subject:   openssl x509 -in cert.crt -noout -subject            │
│ Expiry:    openssl x509 -in cert.crt -noout -dates              │
│ SANs:      openssl x509 -in cert.crt -noout -ext subjectAltName │
│                                                                 │
│ Location:  /etc/pki/tls/certs/ (certificates)                   │
│            /etc/pki/tls/private/ (keys, mode 600!)              │
│                                                                 │
│ Critical:  SANs are REQUIRED on RHEL 9+                         │
│            SHA-256+ signature required on RHEL 8+               │
└─────────────────────────────────────────────────────────────────┘

🧪 Hands-On Lab

Lab 04: X.509 Certificates

Create self-signed certificates, generate CSRs, inspect certificates, and convert formats

  • 📁 Location: labs/en_US/04-x509-certificates/
  • ⏱️ Time: 25-30 minutes
  • 🎯 Level: Beginner

Chapter Navigation

Chapter 6: RHEL Trust Store Deep Dive

Trust Architecture: Understanding how RHEL validates certificates and manages trusted CAs is essential for troubleshooting certificate problems. This chapter goes beyond the basics: it covers the internal mechanics of update-ca-trust, how p11-kit processes trust sources, what happens with duplicate certificates, and how to debug trust issues with trust list.

6.1 RHEL Trust Store Architecture

How RHEL Validates Certificates

When any application on RHEL validates a certificate:

  1. Check certificate signature using issuer’s public key
  2. Find issuer certificate in trust store
  3. Repeat until reaching trusted root CA
  4. Verify root CA is trusted by RHEL system

Trust store location: /etc/pki/ca-trust/

The Role of p11-kit

RHEL’s trust store is not a flat file that applications read directly. It is a managed system built on top of p11-kit, which provides a PKCS#11 trust module. The key components are:

ComponentRole
p11-kitMiddleware that loads trust modules and exposes them via PKCS#11
p11-kit-trustThe trust module (/usr/lib64/pkcs11/p11-kit-trust.so) that reads source certificates
update-ca-trustShell script that invokes p11-kit extract to regenerate extracted bundles
trustCLI front-end to inspect and modify trust objects managed by p11-kit

Applications like curl, wget, OpenSSL, GnuTLS, and NSS all consume the extracted bundles. They never read the source directories directly.


6.2 Trust Store Directory Structure

RHEL 7/8:

/etc/pki/ca-trust/
├── source/
│   ├── anchors/                   ← Admin-added trusted CA certificates
│   ├── blacklist/                 ← Admin-added distrusted certificates
│   └── ca-bundle.legacy.crt       ← Legacy bundle (compatibility only)
├── extracted/
│   ├── pem/
│   │   ├── tls-ca-bundle.pem      ← For TLS clients (curl, wget, Python...)
│   │   ├── email-ca-bundle.pem    ← For S/MIME email validation
│   │   └── objsign-ca-bundle.pem  ← For code signing verification
│   ├── openssl/
│   │   └── ca-bundle.trust.crt    ← OpenSSL "trusted certificate" format
│   ├── java/
│   │   └── cacerts                ← Java JKS keystore
│   └── edk2/
│       └── cacerts.bin            ← UEFI firmware format
└── README

/usr/share/pki/ca-trust-source/
├── anchors/                       ← Package-provided trust anchors (from RPMs)
├── blacklist/                     ← Package-provided distrusted certificates
└── ca-bundle.trust.p11-kit        ← Mozilla CA bundle shipped by ca-certificates RPM

RHEL 9/10+: The blacklist/ directory was renamed to blocklist/:

/etc/pki/ca-trust/
├── source/
│   ├── anchors/                   ← Admin-added trusted CA certificates
│   ├── blocklist/                 ← Admin-added distrusted certificates
│   └── ca-bundle.legacy.crt       ← Legacy bundle (compatibility only)
├── extracted/
│   └── (same structure as above)
└── README

/usr/share/pki/ca-trust-source/
├── anchors/                       ← Package-provided trust anchors (from RPMs)
├── blocklist/                     ← Package-provided distrusted certificates
└── ca-bundle.trust.p11-kit        ← Mozilla CA bundle shipped by ca-certificates RPM

Naming convention: Throughout this chapter, blacklist/ refers to the RHEL 7/8 directory name and blocklist/ refers to the RHEL 9/10+ directory name. Both serve the same purpose. When you see a path like source/blacklist/, substitute source/blocklist/ on RHEL 9+.

Source Directories Priority

The update-ca-trust pipeline reads certificates from multiple source directories, processed in a defined order:

PriorityDirectoryManaged by
1 (lowest)/usr/share/pki/ca-trust-source/RPM packages (ca-certificates)
2 (highest)/etc/pki/ca-trust/source/System administrator

Within each location:

  • anchors/ — Certificates placed here are treated as trusted CAs
  • blacklist/ (RHEL 7/8) or blocklist/ (RHEL 9+) — Certificates placed here are treated as explicitly distrusted

The /etc/pki/ paths always override /usr/share/pki/ paths. This follows the standard RHEL convention: /usr/share/ holds package defaults, /etc/ holds admin customizations.


6.3 What Happens When You Run update-ca-trust

The Execution Pipeline

update-ca-trust is a shell script (inspect it yourself: cat /usr/bin/update-ca-trust). When you execute sudo update-ca-trust, the following sequence occurs:

Step 1: Collect all trust sources

p11-kit reads every certificate file from these directories:

/usr/share/pki/ca-trust-source/anchors/
/usr/share/pki/ca-trust-source/blacklist/    ← RHEL 7/8
/usr/share/pki/ca-trust-source/blocklist/    ← RHEL 9+
/usr/share/pki/ca-trust-source/ca-bundle.trust.p11-kit
/etc/pki/ca-trust/source/anchors/
/etc/pki/ca-trust/source/blacklist/          ← RHEL 7/8
/etc/pki/ca-trust/source/blocklist/          ← RHEL 9+

It accepts PEM (.pem, .crt), DER (.der), and p11-kit trust objects (.p11-kit) formats.

Step 2: Parse trust attributes

For each certificate, p11-kit determines its trust disposition. The PEM format of the certificate file matters:

  • -----BEGIN TRUSTED CERTIFICATE----- (OpenSSL “trusted” format) — Contains the certificate plus auxiliary trust data: explicit lists of trusted and rejected key usage OIDs. p11-kit reads these embedded trust/reject attributes directly.
  • -----BEGIN CERTIFICATE----- (plain PEM) or DER — Contains only the certificate with no trust metadata. p11-kit assigns trust based solely on the directory where the file is placed (anchors/ = trusted, blacklist//blocklist/ = distrusted).
  • .p11-kit format files — Contain PKCS#11 trust objects with fine-grained attributes (trusted, x-distrusted, purpose OIDs). Used by the ca-bundle.trust.p11-kit file shipped with the ca-certificates package.

Critical: The BEGIN TRUSTED CERTIFICATE and BEGIN CERTIFICATE formats are not interchangeable. A BEGIN TRUSTED CERTIFICATE file carries auxiliary trust data — explicit lists of trusted and/or rejected use OIDs. When no uses are listed (empty trust attributes), p11-kit interprets this as “trusted for nothing” — effectively distrusted. If the same certificate also exists as a plain BEGIN CERTIFICATE (which implies trust for all purposes), p11-kit detects contradictory trust assertions and marks the certificate as distrusted. This conflict occurs in two cases: (1) the BEGIN TRUSTED CERTIFICATE has explicit rejected uses, or (2) it has empty trust attributes (no trusted uses, no rejected uses). See Section 6.6 for details.

Step 3: Merge and resolve conflicts

When the same certificate (matched by its DER-encoded content) appears in multiple source locations, p11-kit applies merge rules:

  1. Distrust wins over trust. If a certificate appears in both anchors/ and blacklist//blocklist/, it is distrusted.

  2. Admin overrides packages. Trust attributes set in /etc/pki/ca-trust/source/ override those from /usr/share/pki/ca-trust-source/.

  3. Explicit attributes override defaults. A .p11-kit file with specific purpose restrictions overrides the blanket trust given to a plain PEM in anchors/.

  4. Conflicting trust formats cause distrust. If the same certificate (identical fingerprint) appears as both BEGIN TRUSTED CERTIFICATE (or .p11-kit) and BEGIN CERTIFICATE, distrust occurs in two cases:

    • The BEGIN TRUSTED CERTIFICATE has explicit rejected uses — the rejection contradicts the plain PEM’s implicit full trust.
    • The BEGIN TRUSTED CERTIFICATE has empty trust attributes (no trusted uses, no rejected uses) — p11-kit interprets “no uses listed” as “trusted for nothing,” which contradicts the plain PEM’s implicit “trusted for all purposes.”

    In both cases, p11-kit marks the certificate as distrusted.

This is the single most common cause of unexpected distrust. An administrator copies a plain PEM file into source/anchors/ not realizing that the same certificate already exists in ca-bundle.trust.p11-kit with rejected use attributes or empty trust. The result: the CA is distrusted after update-ca-trust, and services that depend on it break silently.

Step 4: Extract to format-specific bundles

p11-kit runs extraction commands for each output format:

# PEM bundle for TLS (most commonly consumed)
p11-kit extract --format=pem-bundle \
    --filter=ca-anchors \
    --overwrite \
    --purpose=server-auth \
    /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem

# PEM bundle for email (S/MIME)
p11-kit extract --format=pem-bundle \
    --filter=ca-anchors \
    --overwrite \
    --purpose=email-protection \
    /etc/pki/ca-trust/extracted/pem/email-ca-bundle.pem

# PEM bundle for code signing
p11-kit extract --format=pem-bundle \
    --filter=ca-anchors \
    --overwrite \
    --purpose=code-signing \
    /etc/pki/ca-trust/extracted/pem/objsign-ca-bundle.pem

# OpenSSL "trusted certificate" format (includes trust/reject attributes)
p11-kit extract --format=openssl-bundle \
    --filter=certificates \
    --overwrite \
    /etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt

# Java keystore
p11-kit extract --format=java-cacerts \
    --filter=ca-anchors \
    --overwrite \
    --purpose=server-auth \
    /etc/pki/ca-trust/extracted/java/cacerts

# UEFI EDK2 format
p11-kit extract --format=edk2-cacerts \
    --filter=ca-anchors \
    --overwrite \
    --purpose=server-auth \
    /etc/pki/ca-trust/extracted/edk2/cacerts.bin

Step 5: Update compatibility symlinks

The system maintains symlinks so that legacy paths point to the extracted bundles:

/etc/pki/tls/certs/ca-bundle.crt → /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
/etc/pki/tls/certs/ca-bundle.trust.crt → /etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt
/etc/pki/java/cacerts → /etc/pki/ca-trust/extracted/java/cacerts

update-ca-trust vs update-ca-trust extract

These are functionally identical. The update-ca-trust script accepts extract as a subcommand, but running update-ca-trust with no arguments defaults to the extract action. There is no difference in behavior.

# These two commands produce identical results:
sudo update-ca-trust
sudo update-ca-trust extract

The extract subcommand exists for explicitness in scripts and documentation. Historically, update-ca-trust also supported enable and disable subcommands (to switch between the new p11-kit-managed trust store and the legacy flat-file approach), but those are no longer relevant on modern RHEL systems where p11-kit management is always active.

# Check current status (informational only on modern RHEL)
update-ca-trust check

Verifying What Changed

After running update-ca-trust, you can verify the result:

# Count trusted CAs in the PEM bundle
grep -c '^-----BEGIN CERTIFICATE-----' /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem

# List all trusted CAs via p11-kit
trust list --filter=ca-anchors | grep "label:" | wc -l

# Check if a specific CA is present
trust list | grep -A 4 "My Company CA"

6.4 Adding Custom CA Certificates

Step-by-Step

# Step 1: Copy CA certificate (PEM or DER format)
sudo cp company-ca.crt /etc/pki/ca-trust/source/anchors/

# Step 2: Update trust store
sudo update-ca-trust

# Step 3: Verify addition
trust list | grep -i "company"

# Step 4: Test
curl https://internal-server.example.com

Works identically on RHEL 7, 8, 9, 10.

Adding with Purpose Restrictions

If a CA should only be trusted for TLS server authentication (not email signing or code signing), use the trust command instead of the simple file copy approach:

# Trust only for TLS server authentication
sudo trust anchor --store /path/to/company-ca.crt

# Or with explicit purpose restriction via p11-kit format
# Create a .p11-kit file with restricted trust:
cat > /tmp/company-ca.p11-kit <<'EOF'
[p11-kit-object-v1]
class: x-certificate-extension
label: "My Company CA"
x-public-key-info: <extracted-from-cert>

[p11-kit-object-v1]
class: certificate
label: "My Company CA"
certificate-type: x-509
java-midp-security-domain: 0
trusted: true
x-distrusted: false

[p11-kit-object-v1]
class: x-certificate-extension
label: "My Company CA"
object-id: 2.5.29.37
value: "%06%08%2b%06%01%05%05%07%03%01"
EOF

The trust anchor --store command handles this automatically and is the recommended approach on RHEL 8+.


6.5 RHEL Version Trust Store Features

Trust Management Evolution

RHEL Versiontrust CommandDistrustingDistrust DirectoryNotes
RHEL 7BasicLimitedblacklist/Manual management
RHEL 8EnhancedFull supportblacklist/p11-kit integration
RHEL 9EnhancedFull supportblocklist/Renamed from blacklist/
RHEL 10EnhancedFull supportblocklist/Same as RHEL 9

RHEL 8+ Enhancement:

# Advanced trust management (RHEL 8+)
trust anchor /path/to/ca.crt --purpose server-auth
trust anchor --remove "pkcs11:id=%CERT_ID%"
trust list --filter=ca-anchors

# Distrust a compromised CA
# RHEL 7/8:
sudo cp compromised.crt /etc/pki/ca-trust/source/blacklist/
# RHEL 9+:
sudo cp compromised.crt /etc/pki/ca-trust/source/blocklist/

sudo update-ca-trust

6.6 Duplicate Certificates: What Happens and How to Handle Them

PEM Format Variants and Trust Implications

Before discussing duplicates, it is essential to understand the three PEM header formats RHEL uses, because the format itself carries trust semantics:

PEM HeaderTrust DataWhere It Appears
-----BEGIN CERTIFICATE-----None — raw X.509 certificate onlyFiles you download, CSR responses, manual exports
-----BEGIN TRUSTED CERTIFICATE-----Embedded — includes auxiliary trust/reject OID listsOpenSSL extracted bundle (ca-bundle.trust.crt), some vendor-provided files
.p11-kit format (not PEM)Structured — PKCS#11 trust objects with fine-grained attributesca-bundle.trust.p11-kit shipped by ca-certificates RPM

You can inspect the trust attributes embedded in a BEGIN TRUSTED CERTIFICATE file:

# Show the auxiliary trust data
openssl x509 -in cert.crt -noout -text -trustout 2>/dev/null | grep -A 5 "Trusted Uses\|Rejected Uses"

A BEGIN TRUSTED CERTIFICATE file with “Rejected Uses: TLS Web Server Authentication” and a BEGIN CERTIFICATE file for the same cert (which carries no rejection information) are contradictory from p11-kit’s perspective. The same applies to a BEGIN TRUSTED CERTIFICATE with no trust/reject uses at all — p11-kit interprets empty trust as “trusted for nothing,” which contradicts the implicit full trust of a plain BEGIN CERTIFICATE.

Understanding “Duplicate” Certificates

Two certificates can be duplicates at different levels:

Match LevelWhat It MeansHow p11-kit Treats It
Identical DER encoding, same formatByte-for-byte same certificate, same trust wrappingDeduplicated — appears once in output
Identical DER encoding, different trust formatSame certificate but one has BEGIN TRUSTED CERTIFICATE / .p11-kit attributes and the other has BEGIN CERTIFICATEDISTRUSTED if the trusted format has rejected uses OR empty trust attributes (no uses listed = “trusted for nothing”)
Same Subject + Serial + Issuer, different DERSame logical certificate but re-encodedTreated as separate objects — both are loaded
Same Subject DN, different SerialDifferent certificates for the same entity (e.g., reissued CA)Both valid, both loaded independently

The critical distinction: p11-kit matches duplicates by DER-encoded certificate content, not by metadata fields. However, “matching” the DER content is only half the story — what happens next depends entirely on whether the trust wrapping formats agree:

  • If the raw certificate DER bytes are identical and all sources agree on trust disposition → deduplicated, trusted
  • If the raw certificate DER bytes are identical but one source has rejected use OIDs embedded and the other does not → contradictory trust assertions → DISTRUSTED
  • If the raw certificate DER bytes are identical but one source has BEGIN TRUSTED CERTIFICATE with empty trust attributes (no trusted uses, no rejected uses) → p11-kit interprets this as “trusted for nothing” → contradicts implicit full trust → DISTRUSTED
  • If the raw certificate DER bytes are identical and one source has BEGIN TRUSTED CERTIFICATE with explicit trusted uses only (no rejected uses) → certificate is accepted with the specified trust purposes
  • If the raw certificate DER bytes differ (even by a single byte) → p11-kit treats them as separate certificates and both are included

Scenario: Conflicting Trust Formats (Most Dangerous)

This is the single most common and most damaging “duplicate” problem. It happens silently when an administrator copies a certificate into source/anchors/ not realizing the same certificate already exists in the system bundle with rejected use OIDs or empty trust attributes in its trust data.

Example:

/usr/share/pki/ca-trust-source/ca-bundle.trust.p11-kit
  → Contains "My Corp CA" as a p11-kit trust object with specific trust attributes
    INCLUDING rejected uses (e.g., "Rejected Uses: TLS Web Server Authentication")
    OR with empty trust attributes (no trusted uses, no rejected uses listed)

/etc/pki/ca-trust/source/anchors/my-corp-ca.crt
  → Same "My Corp CA" as plain PEM (-----BEGIN CERTIFICATE-----)
    (no trust attributes — relies on directory placement for implicit trust)

Result: The same certificate DER content now has contradictory trust descriptions. The p11-kit trust object either explicitly rejects certain uses, or lists no uses at all (which p11-kit interprets as “trusted for nothing”). The plain PEM carries no trust metadata, implying trust for all purposes. p11-kit cannot reconcile these contradictions, and marks the certificate as distrusted. After running update-ca-trust:

  • The certificate disappears from tls-ca-bundle.pem
  • trust list shows it with trust: distrusted
  • All services relying on this CA start failing with certificate verify failed

This same problem occurs with the OpenSSL BEGIN TRUSTED CERTIFICATE format when it contains rejected uses OR empty trust:

/etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt
  → Contains the certificate as -----BEGIN TRUSTED CERTIFICATE-----
    (with "Rejected Uses" OIDs embedded, OR with no trust/reject uses at all)

/etc/pki/ca-trust/source/anchors/certificate.crt
  → Same certificate as -----BEGIN CERTIFICATE-----
    (no embedded trust data — no rejection information)

Result: Same outcome — the rejection or empty trust in one source contradicts the implicit full trust in the other, certificate marked as distrusted.

Key insight: In a BEGIN TRUSTED CERTIFICATE, “no trust/reject uses listed” does NOT mean “trusted for all purposes.” It means “trusted for nothing.” This is the critical distinction from a plain BEGIN CERTIFICATE in anchors/, where directory placement grants implicit full trust. To debug, search for duplicate certificates across /etc/pki/ca-trust/source/ and /usr/share/pki/ca-trust-source/ and check if any BEGIN TRUSTED CERTIFICATE exists with no Reject or empty trust that would conflict with a plain PEM copy.

How to fix it:

# Option 1: Remove the plain PEM duplicate from anchors/
sudo rm /etc/pki/ca-trust/source/anchors/my-corp-ca.crt
sudo update-ca-trust

# Option 2: If you NEED the cert in anchors/, convert it to the
# trusted format matching the existing trust attributes:
openssl x509 -in my-corp-ca.crt -addtrust serverAuth \
    -addtrust emailProtection -out my-corp-ca-trusted.crt
sudo cp my-corp-ca-trusted.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

# Verify the fix
trust list --filter=ca-anchors | grep -A 3 "My Corp CA"

Scenario: Explicit Distrusting

/usr/share/pki/ca-trust-source/ca-bundle.trust.p11-kit
  → Contains "Legacy Corp CA" with full trust

/etc/pki/ca-trust/source/blacklist/legacy-corp-ca.crt    ← RHEL 7/8
/etc/pki/ca-trust/source/blocklist/legacy-corp-ca.crt    ← RHEL 9+
  → Same "Legacy Corp CA" as plain PEM, placed in distrust directory

Result: Distrust wins by design. The certificate is excluded from tls-ca-bundle.pem and marked as distrusted in the OpenSSL bundle. This is the expected behavior when an admin intentionally distrusts a CA.

Scenario: Nearly Identical Certificates (Different DER)

A subtler problem occurs when two certificates look the same but are not byte-for-byte identical:

  • A CA certificate was downloaded from two different sources with slightly different PEM line wrapping
  • A CA certificate was re-encoded (PEM → DER → PEM) and gained different header metadata
  • A CA was reissued with the same Subject DN but a new key pair and serial number

In these cases, p11-kit treats them as distinct certificates, and both end up in the extracted bundles. This can cause:

  • Confusing trust list output with apparent duplicates
  • Increased bundle size (cosmetic issue)
  • Chain building confusion if one copy is distrusted and the other is trusted but they have different DER content

How to Detect Duplicate Certificates

Method 1: Fingerprint + format comparison

Extract fingerprints from all source certificates and check for duplicates, including what format each file uses:

# Scan all source directories for duplicate SHA-256 fingerprints
# AND report the PEM format of each file
for dir in /usr/share/pki/ca-trust-source/anchors \
           /etc/pki/ca-trust/source/anchors; do
    for cert in "$dir"/*.crt "$dir"/*.pem 2>/dev/null; do
        [ -f "$cert" ] || continue
        fp=$(openssl x509 -in "$cert" -noout -fingerprint -sha256 2>/dev/null)
        fmt=$(head -1 "$cert" 2>/dev/null)
        [ -n "$fp" ] && echo "$fp  [$fmt]  $cert"
    done
done | sort

# Look for same fingerprint appearing with different formats:
# If you see the same fingerprint with both "BEGIN CERTIFICATE" and
# "BEGIN TRUSTED CERTIFICATE", that is the source of a trust conflict.

If the same fingerprint appears with different PEM headers, you have found a trust format conflict that will cause distrust.

Method 2: Using trust list to spot duplicates

# Extract all labels and look for duplicates
trust list --filter=ca-anchors | grep "^    label:" | sort | uniq -c | sort -rn | head -20

If any label appears more than once, investigate further:

# Show full details for a suspected duplicate
trust list | grep -B 2 -A 10 "label: DigiCert Global Root G2"

Method 3: Compare the extracted bundle against source files

# Count certificates in the PEM bundle
grep -c 'BEGIN CERTIFICATE' /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem

# Count unique certificates by fingerprint
awk '/BEGIN CERT/,/END CERT/' /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem | \
    csplit -z -f /tmp/cert- - '/BEGIN CERTIFICATE/' '{*}' 2>/dev/null
for f in /tmp/cert-*; do
    openssl x509 -in "$f" -noout -fingerprint -sha256 2>/dev/null
done | sort -u | wc -l
rm -f /tmp/cert-*

If the certificate count exceeds the unique fingerprint count, duplicates exist in the extracted bundle.

How to Identify Differences Between Suspected Duplicates

When you have two certificate files that appear to be the same CA, compare them at four levels:

Level 1: Check the PEM format (most common cause of conflicts)

# Check what PEM header each file uses
head -1 cert1.crt
head -1 cert2.crt

# "-----BEGIN CERTIFICATE-----"         → plain PEM, no trust data
# "-----BEGIN TRUSTED CERTIFICATE-----" → OpenSSL trusted format, HAS trust data

If one says BEGIN CERTIFICATE and the other says BEGIN TRUSTED CERTIFICATE, you have found the problem. They will cause a trust conflict even if the underlying certificate is identical.

Level 2: Compare DER-encoded certificate content

# Compare DER-encoded content (strips PEM headers, whitespace differences)
openssl x509 -in cert1.crt -outform DER -out /tmp/cert1.der
openssl x509 -in cert2.crt -outform DER -out /tmp/cert2.der
diff /tmp/cert1.der /tmp/cert2.der && echo "IDENTICAL DER" || echo "DIFFERENT DER"

Level 3: Compare certificate fields

# Compare subject, issuer, serial, validity, and key
for field in subject issuer serial dates fingerprint pubkey; do
    echo "=== $field ==="
    echo "cert1: $(openssl x509 -in cert1.crt -noout -$field 2>/dev/null)"
    echo "cert2: $(openssl x509 -in cert2.crt -noout -$field 2>/dev/null)"
done

Level 4: Compare embedded trust attributes (if TRUSTED CERTIFICATE format)

# Show trust attributes for each file
echo "=== cert1 trust attributes ==="
openssl x509 -in cert1.crt -noout -text -trustout 2>/dev/null | grep -A 5 "Trusted Uses\|Rejected Uses"
echo "=== cert2 trust attributes ==="
openssl x509 -in cert2.crt -noout -text -trustout 2>/dev/null | grep -A 5 "Trusted Uses\|Rejected Uses"

Common findings:

ObservationLikely ExplanationImpact
Same fingerprint, same PEM formatHarmless duplicate — p11-kit deduplicatesNone
Same fingerprint, different PEM format (trusted format has rejected uses OR empty trust)Trust contradiction — rejected uses or “trusted for nothing” vs implicit full trustCertificate marked DISTRUSTED
Same subject + serial, different fingerprintRe-encoded or tampered certificateBoth loaded as separate objects
Same subject, different serialReissued CA certificate (new key pair or renewed)Both loaded independently
Same subject + serial + fingerprint, different trust list outputSame certificate with different trust attributes appliedPossible distrust

Cleaning Up Duplicates

If the certificate is already in the system bundle (most common case):

A certificate in anchors/ that already exists in ca-bundle.trust.p11-kit is not always harmless — if the system bundle copy has rejected use OIDs or empty trust attributes, the plain PEM duplicate causes distrust. Always check the trust attributes before assuming a duplicate is benign.

# 1. Check the format of the file in anchors/
head -1 /etc/pki/ca-trust/source/anchors/suspect.crt

# 2. Find the fingerprint
openssl x509 -in /etc/pki/ca-trust/source/anchors/suspect.crt -noout -fingerprint -sha256

# 3. Check if that certificate exists in the system bundle
trust list | grep -B5 -A5 "<subject from the cert>"

# 4. If the certificate already exists in the system bundle with proper
#    trust attributes, remove the duplicate from anchors/:
sudo rm /etc/pki/ca-trust/source/anchors/suspect.crt
sudo update-ca-trust

# 5. Verify the certificate is now trusted (not distrusted)
trust list --filter=ca-anchors | grep -A 3 "<subject from the cert>"

If you need to keep the certificate in anchors/ (custom CA not in system bundle):

# Ensure the file format matches what p11-kit expects.
# For custom CAs not in the system bundle, plain PEM is fine:
openssl x509 -in suspect.crt -out /etc/pki/ca-trust/source/anchors/suspect.crt
sudo update-ca-trust

6.7 Using trust list to Identify Distrusted Certificates

The trust list command is the primary tool for inspecting the state of the trust store after update-ca-trust has been run.

Basic Usage

# List all trust objects (trusted + distrusted)
trust list

# List only trusted CA anchors
trust list --filter=ca-anchors

# List only distrusted certificates
trust list --filter=blacklist     # RHEL 7/8
trust list --filter=blocklist     # RHEL 9+

# List all certificates (without filtering by trust disposition)
trust list --filter=certificates

Anatomy of trust list Output

Each entry in trust list output looks like this:

pkcs11:id=%DE%28%F4%A4%FF%E5%B9%2F%A3%C5%03%D1%A3%49%A7%F9%96%2A%82%12;type=cert
    type: certificate
    label: DigiCert Global Root G2
    trust: anchor
    category: authority

pkcs11:id=%01%02%03...;type=cert
    type: certificate
    label: Legacy Compromised CA
    trust: distrusted
    category: authority
FieldMeaning
pkcs11:id=...PKCS#11 URI uniquely identifying this object
typeAlways certificate for CA certs
labelHuman-readable name (CN from the certificate subject)
trust: anchorCertificate is trusted as a CA
trust: distrustedCertificate is explicitly distrusted
category: authorityCertificate is a CA (has Basic Constraints CA:TRUE)
category: other-entryCertificate is an end-entity or unclassified

Finding Distrusted Certificates

# List all distrusted certificates with their labels
trust list --filter=blacklist     # RHEL 7/8
trust list --filter=blocklist     # RHEL 9+

# Count distrusted certificates
trust list --filter=blocklist | grep "^pkcs11:" | wc -l    # RHEL 9+

# Search for a specific distrusted certificate
trust list --filter=blocklist | grep -B 1 -A 4 "Symantec"  # RHEL 9+

Tracing a Distrusted Certificate to Its Source

When trust list --filter=blacklist (RHEL 7/8) or trust list --filter=blocklist (RHEL 9+) shows a certificate you did not expect to be distrusted, you need to find where the distrust originates:

# Step 1: Get the label of the distrusted certificate
trust list --filter=blacklist     # RHEL 7/8
trust list --filter=blocklist     # RHEL 9+
# Example output:
# pkcs11:id=%AB%CD...;type=cert
#     type: certificate
#     label: Suspicious CA
#     trust: distrusted
#     category: authority

# Step 2: Check admin distrust directory
# RHEL 7/8: blacklist/  |  RHEL 9+: blocklist/
for distrust_dir in /etc/pki/ca-trust/source/blacklist \
                    /etc/pki/ca-trust/source/blocklist; do
    [ -d "$distrust_dir" ] || continue
    echo "=== $distrust_dir ==="
    ls -la "$distrust_dir"/
    for f in "$distrust_dir"/*; do
        [ -f "$f" ] || continue
        subj=$(openssl x509 -in "$f" -noout -subject 2>/dev/null)
        echo "$f: $subj"
    done
done

# Step 3: Check package-provided distrust directory
for distrust_dir in /usr/share/pki/ca-trust-source/blacklist \
                    /usr/share/pki/ca-trust-source/blocklist; do
    [ -d "$distrust_dir" ] || continue
    echo "=== $distrust_dir ==="
    ls -la "$distrust_dir"/
    for f in "$distrust_dir"/*; do
        [ -f "$f" ] || continue
        subj=$(openssl x509 -in "$f" -noout -subject 2>/dev/null)
        echo "$f: $subj"
    done
done

# Step 4: Check the main p11-kit bundle for inline distrust attributes
grep -A 5 "Suspicious CA" /usr/share/pki/ca-trust-source/ca-bundle.trust.p11-kit
# Look for "x-distrusted: true" or "nss-mozilla-ca-policy: false"

Interpretation of results:

Found inMeaningAction
/etc/pki/ca-trust/source/blacklist/ (RHEL 7/8) or blocklist/ (RHEL 9+)Admin explicitly distrusted itIntentional — verify with team if unexpected
/usr/share/pki/ca-trust-source/blacklist/ (RHEL 7/8) or blocklist/ (RHEL 9+)RPM package distrusted itMozilla/Red Hat revoked trust — check security advisories
ca-bundle.trust.p11-kit with distrust attributesMozilla removed trust upstreamNormal — CA was distrusted by Mozilla NSS root program
Not in any distrust directoryLikely a trust format conflict — same cert exists as both BEGIN CERTIFICATE and BEGIN TRUSTED CERTIFICATE / .p11-kitCheck source/anchors/ for a plain PEM duplicate of a cert already in the system bundle (Section 6.6)

Practical Example: Debugging a Distrusted CA

A service fails with certificate verify failed and you suspect the CA was distrusted:

# 1. Identify the CA from the failing certificate
openssl x509 -in /etc/pki/tls/certs/server.crt -noout -issuer
# issuer=CN = Internal Corp CA, O = CorpCo

# 2. Check if the issuer is trusted
trust list --filter=ca-anchors | grep -A 3 "Internal Corp CA"
# No output means it's NOT in the trusted anchors

# 3. Check if it's actively distrusted
trust list --filter=blacklist | grep -A 3 "Internal Corp CA"   # RHEL 7/8
trust list --filter=blocklist | grep -A 3 "Internal Corp CA"   # RHEL 9+
# If found here, the CA is explicitly distrusted

# 4. Check if it's present at all
trust list | grep -A 3 "Internal Corp CA"
# If not found anywhere, it was never added to the trust store

# 5. If distrusted, find the source file (checks both RHEL 7/8 and 9+ paths)
find /etc/pki/ca-trust/source/blacklist/ \
     /etc/pki/ca-trust/source/blocklist/ \
     /usr/share/pki/ca-trust-source/blacklist/ \
     /usr/share/pki/ca-trust-source/blocklist/ \
     -type f 2>/dev/null | while read f; do
    if openssl x509 -in "$f" -noout -subject 2>/dev/null | grep -qi "Internal Corp CA"; then
        echo "FOUND: $f"
    fi
done

# 6. If NOT found in any distrust directory, check for trust format conflicts:
#    look for a plain PEM in anchors/ that duplicates a cert in the system bundle
for f in /etc/pki/ca-trust/source/anchors/*; do
    [ -f "$f" ] || continue
    if openssl x509 -in "$f" -noout -subject 2>/dev/null | grep -qi "Internal Corp CA"; then
        echo "DUPLICATE IN ANCHORS: $f"
        echo "  Format: $(head -1 "$f")"
        echo "  Check if the same cert exists in ca-bundle.trust.p11-kit with different trust format"
    fi
done

# 7. Also check the main p11-kit bundle
grep -B 2 -A 10 "Internal Corp CA" \
    /usr/share/pki/ca-trust-source/ca-bundle.trust.p11-kit

Restoring Trust for a Distrusted Certificate

If you determine a certificate was incorrectly distrusted:

# If the distrust entry is in /etc/pki/ (admin-managed)
# RHEL 7/8:
sudo rm /etc/pki/ca-trust/source/blacklist/the-cert.crt
# RHEL 9+:
sudo rm /etc/pki/ca-trust/source/blocklist/the-cert.crt

sudo update-ca-trust

# If the distrust comes from the ca-certificates package, you need to
# override it by adding the certificate as a trusted anchor:
sudo cp the-cert.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust
# Note: this works because admin anchors override package-level distrust
# for certificates matched by DER content.

# Verify the certificate is now trusted
trust list --filter=ca-anchors | grep -A 3 "The Cert Label"

Warning: Overriding a Mozilla/Red Hat distrust decision should only be done if you have a specific business reason and understand the security implications. CAs are distrusted for cause (compromise, mis-issuance, policy violations).


6.8 Advanced Debugging with trust and p11-kit

Inspecting Individual Trust Objects

# Dump the full PKCS#11 attributes for a specific certificate
trust dump --filter="pkcs11:id=%DE%28%F4%A4..." 

# List all attributes of all trust objects (verbose, large output)
trust dump

Verifying the Extraction Pipeline

If you suspect update-ca-trust is not producing the expected output:

# Run the extraction manually with verbose output
p11-kit extract --format=pem-bundle \
    --filter=ca-anchors \
    --purpose=server-auth \
    /tmp/test-tls-bundle.pem

# Compare with the system bundle
diff <(sort /tmp/test-tls-bundle.pem) \
     <(sort /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem)

# Run with p11-kit debug logging
P11_KIT_DEBUG=all update-ca-trust 2>&1 | head -100

Checking Certificate Chain Completeness

# Verify a server certificate against the system trust store
openssl verify -CAfile /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem server.crt

# If the chain has intermediates, include them:
openssl verify -CAfile /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem \
    -untrusted intermediate.crt server.crt

# Show the full chain that OpenSSL would build:
openssl verify -show_chain -CAfile /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem server.crt

Cross-checking the OpenSSL Bundle

The OpenSSL “trusted certificate” format (ca-bundle.trust.crt) includes embedded trust/reject attributes that are distinct from the plain PEM bundle. You can inspect them:

# Show trust attributes for certificates in the OpenSSL bundle
openssl x509 -in /etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt \
    -noout -text -trustout 2>/dev/null | grep -A 2 "Trusted Uses\|Rejected Uses"

Checking the Java Keystore

# List all entries in the Java trust store
keytool -list -cacerts -storepass changeit 2>/dev/null | grep "trustedCertEntry" | wc -l

# Search for a specific CA
keytool -list -cacerts -storepass changeit 2>/dev/null | grep -i "company"

6.9 Troubleshooting Trust Issues

Systematic Approach

Symptom: Certificate verification failed

Step 1: Identify what certificate is failing and who issued it

# Get the issuer chain from a remote server
openssl s_client -connect server.example.com:443 -showcerts </dev/null 2>/dev/null | \
    openssl x509 -noout -issuer -subject

# Or from a local certificate file
openssl x509 -in server.crt -noout -issuer -subject -serial

Step 2: Check if the issuer CA is in the trust store

trust list --filter=ca-anchors | grep -i "ISSUER_CN_HERE"

Step 3: Check if the issuer CA is distrusted

trust list --filter=blacklist | grep -i "ISSUER_CN_HERE"    # RHEL 7/8
trust list --filter=blocklist | grep -i "ISSUER_CN_HERE"    # RHEL 9+

Step 4: If missing, add it

sudo cp issuer-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

Step 5: If distrusted, find the source and decide on action

# Trace the distrust source (see Section 6.7)
# Checks both RHEL 7/8 (blacklist) and RHEL 9+ (blocklist) paths
find /etc/pki/ca-trust/source/blacklist/ \
     /etc/pki/ca-trust/source/blocklist/ \
     /usr/share/pki/ca-trust-source/blacklist/ \
     /usr/share/pki/ca-trust-source/blocklist/ \
     -name '*.crt' -o -name '*.pem' 2>/dev/null | while read f; do
    openssl x509 -in "$f" -noout -subject 2>/dev/null
done

Common Trust Store Problems

ProblemCauseSolution
CA not found after adding to anchors/Forgot to run update-ca-trustRun sudo update-ca-trust
CA distrusted after adding to anchors/Plain PEM conflicts with existing TRUSTED CERTIFICATE or .p11-kit format that has rejected uses or empty trustRemove the duplicate from anchors/ — the system bundle already has it (Section 6.6)
CA still distrusted after adding to anchors/DER content differs from distrusted copyCompare fingerprints; ensure identical certificate
Java app doesn’t trust CAJava keystore not regeneratedRun sudo update-ca-trust (rebuilds cacerts)
Trust restored after rebootAdmin added cert to /usr/share/ (overwritten by RPM updates)Always use /etc/pki/ca-trust/source/anchors/
trust list shows duplicate entriesSame subject CA from multiple sources with different DER contentIdentify and remove the redundant source file
update-ca-trust silently failsCorrupted certificate file in sourcesCheck for syntax errors: openssl x509 -in suspect.crt -noout

Quick Reference

┌───────────────────────────────────────────────────────────────────────────────┐
│ RHEL TRUST STORE QUICK REFERENCE                                              │
├───────────────────────────────────────────────────────────────────────────────┤
│                                                                               │
│  Add CA:       sudo cp ca.crt /etc/pki/ca-trust/source/anchors/               │
│                sudo update-ca-trust                                           │
│                                                                               │
│  Distrust:     sudo cp bad.crt /etc/pki/ca-trust/source/blacklist/            │
│                (RHEL 7/8) or .../source/blocklist/ (RHEL 9+)                  │
│                sudo update-ca-trust                                           │
│                                                                               │
│  Verify:       trust list --filter=ca-anchors | grep "CA Name"                │
│                openssl verify -CAfile /etc/pki/ca-trust/extracted/            │
│                    pem/tls-ca-bundle.pem cert.crt                             │
│                                                                               │
│  Distrusted:   trust list --filter=blacklist  (RHEL 7/8)                      │
│                trust list --filter=blocklist  (RHEL 9+)                       │
│                                                                               │
│  Duplicates:   trust list --filter=ca-anchors | grep "label:" |               │
│                    sort | uniq -c | sort -rn                                  │
│                                                                               │
│  Compare:      openssl x509 -in a.crt -outform DER | sha256sum                │
│                openssl x509 -in b.crt -outform DER | sha256sum                │
│                                                                               │
│  Debug:        P11_KIT_DEBUG=all update-ca-trust 2>&1                         │
│                                                                               │
│  Source dirs:  /etc/pki/ca-trust/source/{anchors,blacklist|blocklist}/        │
│                /usr/share/pki/ca-trust-source/{anchors,blacklist|blocklist}/  │
│                                                                               │
│  Extracted:    /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem              │
│                /etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt        │
│                /etc/pki/ca-trust/extracted/java/cacerts                       │
│                                                                               │
│  Pipeline:     update-ca-trust = update-ca-trust extract (identical)          │
│                                                                               │
│  Priority:     blacklist/blocklist > anchors                                  │
│                /etc/pki/ > /usr/share/pki/                                    │
│                .p11-kit attributes > plain PEM defaults                       │
│                                                                               │
│  DANGER:       Never add a plain "BEGIN CERTIFICATE" PEM to anchors/          │
│                if the same cert already exists in the system bundle           │
│                as "BEGIN TRUSTED CERTIFICATE" or .p11-kit format.             │
│                Empty trust (no uses) = "trusted for nothing" = DISTRUSTED.    │
│                Rejected uses + plain PEM → also DISTRUSTED.                   │
└───────────────────────────────────────────────────────────────────────────────┘

🧪 Hands-On Lab

Lab 05: Trust Store Management

Add custom CAs to the system trust store, manage trust attributes, detect duplicates, and debug distrusted certificates.

  • 📁 Location: labs/en_US/05-trust-store/
  • ⏱️ Time: 45 minutes
  • 🎯 Level: Intermediate

Chapter Navigation

Chapter 7: Digital Signatures & Verification on RHEL

How Trust Works: Learn how digital signatures enable certificate validation on RHEL systems.

7.1 Cryptographic Hash Functions

Properties:

  1. Deterministic
  2. Pre-image resistance
  3. Collision resistance
  4. Avalanche effect

Popular algorithms: SHA-256, SHA-3, BLAKE2.

7.2 Building Signatures

  1. Calculate hash of message.
  2. Encrypt hash with private key → signature.
  3. Receiver decrypts signature with public key and compares with their own hash.

7.3 Certificate Fingerprints

A fingerprint is simply the hash of the DER-encoded certificate, used to uniquely identify it, e.g.,

openssl x509 -in server.crt -noout -fingerprint -sha256

7.4 Lab: Sign & Verify a File

# sign
openssl dgst -sha256 -sign rsa.key.pem -out report.sig report.pdf
# verify
openssl dgst -sha256 -verify rsa.pub.pem -signature report.sig report.pdf

7.5 Summary

Signatures tie data to identities; hashes ensure integrity. Together they underpin certificate validation and all PKI operations.


7.6 Signature Algorithms on RHEL

Approved by RHEL Version

AlgorithmRHEL 7RHEL 8RHEL 9RHEL 10
SHA-256✅ Yes✅ Yes✅ Yes✅ Yes
SHA-384✅ Yes✅ Yes✅ Yes✅ Yes
SHA-512✅ Yes✅ Yes✅ Yes✅ Yes
SHA-1✅ Yes⚠️ Deprecated❌ Blocked❌ Blocked
MD5✅ Yes⚠️ Legacy only❌ Blocked❌ Blocked

Critical: RHEL 9+ blocks SHA-1 and MD5 for security!

Verifying Certificates on RHEL

# Verify certificate chain
openssl verify /etc/pki/tls/certs/server.crt

# Verify against specific CA
openssl verify -CAfile /etc/pki/tls/certs/ca-bundle.crt server.crt

# Check signature algorithm
openssl x509 -in server.crt -noout -text | grep "Signature Algorithm"
# Must be SHA-256+ on RHEL 8+

Quick Reference

┌──────────────────────────────────────────────────────────┐
│ DIGITAL SIGNATURES ON RHEL                               │
├──────────────────────────────────────────────────────────┤
│ Purpose:      Prove authenticity and integrity           │
│ How:          Hash + Private key = Signature             │
│ Verify:       Signature + Public key = Original hash     │
│                                                          │
│ Approved:     SHA-256, SHA-384, SHA-512                  │
│ Deprecated:   SHA-1 (blocked on RHEL 9+)                 │
│ Blocked:      MD5 (blocked on RHEL 9+)                   │
│                                                          │
│ Verify cert:  openssl verify cert.crt                    │
│ Check alg:    openssl x509 -noout -text | grep Signature │
│ Fingerprint:  openssl x509 -noout -fingerprint -sha256   │
└──────────────────────────────────────────────────────────┘

🧪 Hands-On Lab

Lab 03: Digital Signatures

Sign files, verify signatures, and detect tampering

  • 📁 Location: labs/en_US/03-digital-signatures/
  • ⏱️ Time: 20 minutes
  • 🎯 Level: Beginner

Chapter Navigation

Chapter 8: RHEL Versions & Certificate Evolution

Learning Objective: Understand how certificate management differs across RHEL 7, 8, 9, and 10 so you can quickly identify version-specific behaviors when troubleshooting.


8.1 Why RHEL Version Matters

When troubleshooting certificate issues on RHEL, the first question should always be: “What RHEL version am I on?”

Certificate management has evolved significantly across RHEL versions. What works on RHEL 7 may fail on RHEL 9. Understanding these differences is crucial for:

  • ✅ Choosing the right troubleshooting approach
  • ✅ Identifying version-specific errors
  • ✅ Planning migrations
  • ✅ Writing compatible automation scripts

8.2 Quick Version Check

# Method 1: Check /etc/redhat-release
cat /etc/redhat-release
# Output examples:
#   Red Hat Enterprise Linux Server release 7.9 (Maipo)
#   Red Hat Enterprise Linux release 8.10 (Ootpa)
#   Red Hat Enterprise Linux release 9.8 (Plow)
#   Red Hat Enterprise Linux release 10.2 (Coughlan)

# Method 2: Use rpm
rpm -q --queryformat '%{VERSION}\n' redhat-release

# Method 3: Check OpenSSL version (indirect but useful)
openssl version
# RHEL 7: OpenSSL 1.0.2k family
# RHEL 8: OpenSSL 1.1.1 family
# RHEL 9: OpenSSL 3.x
# RHEL 10: OpenSSL 3.x

8.3 RHEL Version Overview

RHEL VersionGA DateLifecycle NoteOpenSSL VersionKey Certificate Feature
RHEL 7June 2014June 2024 base life cycle; extended options vary1.0.2k familyTraditional manual management
RHEL 8May 2019May 20291.1.1 familyCrypto-policies introduced
RHEL 9May 2022May 20323.xOpenSSL 3.x, stricter defaults
RHEL 10May 2025May 20353.xContinued hardening, PQC prep

Source: Red Hat Product Life Cycle

Use the product life cycle page for exact EUS/ELS/add-on details; the table above is a major-version planning view, not a contract summary.


8.4 Major Differences Summary

RHEL 7 (Legacy)

Characteristics:

  • ✅ Stable, well-understood
  • ✅ Maximum compatibility
  • ⚠️ TLS 1.0/1.1 enabled by default
  • ⚠️ Weak ciphers allowed
  • ⚠️ Manual certificate management

Package Family: openssl-1.0.2k*

When You’ll See It:

  • Legacy systems not yet migrated
  • Applications requiring old TLS versions
  • Conservative environments

Key Command:

# Generate key (RHEL 7 style)
openssl genrsa -out server.key 2048

RHEL 8 (Widely Deployed)

Characteristics:

  • System-wide crypto-policies (game changer!)
  • ✅ TLS 1.2+ by default
  • ✅ certmonger for auto-renewal
  • ✅ Modern cipher suites
  • ⚠️ Breaking changes from RHEL 7

Package Family: openssl-1.1.1*

Key Innovation - Crypto-Policies:

# View current policy
update-crypto-policies --show
# DEFAULT, LEGACY, FUTURE, or FIPS

# System-wide security control!
sudo update-crypto-policies --set FUTURE

When You’ll See It:

  • Most enterprise deployments
  • Modern applications
  • FreeIPA environments

Key Command:

# Generate key (RHEL 8 modern style)
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out server.key

RHEL 9 (Modern Standard)

Characteristics:

  • ✅ OpenSSL 3.5.5 with provider architecture
  • ✅ TLS 1.2+ mandatory
  • ✅ Enhanced crypto-policies
  • ✅ Stricter certificate validation
  • ⚠️ OpenSSL 3.x API changes
  • ⚠️ Legacy algorithms disabled

Package Family: openssl-3*

Major Change - Provider Architecture:

# List crypto providers
openssl list -providers
# default, fips, legacy, base

# Legacy algorithms require explicit provider
openssl md5 -provider legacy file.txt

When You’ll See It:

  • New deployments
  • Security-conscious environments
  • Latest application versions

Key Command:

# Generate EC key (RHEL 9)
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ec.key

RHEL 10 (Current Release)

Characteristics:

  • ✅ Same OpenSSL 3.5.5 as RHEL 9.8
  • ✅ Continued security hardening
  • ✅ Post-quantum cryptography preparation
  • ✅ Enhanced container certificate support
  • ⚠️ Even stricter defaults
  • ⚠️ Additional legacy removals

Package Family: openssl-3*

Note: RHEL 10.0 GA was May 20, 2025. Features and capabilities may evolve across minor versions (10.1, 10.2, etc.). Always consult official documentation for your specific RHEL 10.x release.

Key Focus:

  • Quantum-resistant cryptography foundation
  • Modern security practices
  • Container and cloud-native workloads

When You’ll See It:

  • Brand new deployments
  • Cutting-edge security requirements
  • Future-proofing initiatives

8.5 Critical Version Differences

TLS Version Support

TLS VersionRHEL 7RHEL 8RHEL 9RHEL 10
TLS 1.0✅ Yes⚠️ LEGACY only❌ No❌ No
TLS 1.1✅ Yes⚠️ LEGACY only❌ No❌ No
TLS 1.2✅ Yes✅ Yes✅ Yes✅ Yes
TLS 1.3❌ No✅ Yes✅ Yes (preferred)✅ Yes (preferred)

Certificate Tools Availability

ToolRHEL 7RHEL 8RHEL 9RHEL 10
openssl1.0.2k1.1.1k3.5.53.5.5
certutil (NSS)✅ Yes✅ Yes✅ Yes✅ Yes
update-ca-trust✅ Yes✅ Enhanced✅ Enhanced✅ Enhanced
certmonger✅ Yes✅ Enhanced✅ Enhanced✅ Enhanced
crypto-policies❌ No✅ Yes✅ Enhanced✅ Enhanced
authconfig✅ Yes❌ No (use authselect)❌ No❌ No

Key Cipher/Algorithm Changes

Algorithm/FeatureRHEL 7RHEL 8RHEL 9RHEL 10
3DES✅ Yes⚠️ LEGACY❌ No❌ No
RC4✅ Yes❌ No❌ No❌ No
MD5 signatures✅ Yes⚠️ LEGACY❌ No❌ No
SHA-1 signatures✅ Yes⚠️ Deprecated❌ No❌ No
RSA < 2048 bits✅ Yes❌ No❌ No❌ No
DSA keys✅ Yes⚠️ LEGACY❌ No❌ No

8.6 Common Version-Specific Issues

RHEL 7 Issues

# Problem: Old cipher suites accepted
# Impact: Security vulnerabilities
# Solution: Manual Apache/NGINX cipher configuration

RHEL 8 Issues

# Problem: Application fails after migration from RHEL 7
# Reason: TLS 1.0/1.1 disabled by default
# Quick Fix: Temporarily use LEGACY policy (not recommended long-term)
sudo update-crypto-policies --set LEGACY

# Better Fix: Update application to support TLS 1.2+

RHEL 9 Issues

# Problem: OpenSSL commands fail with provider errors
# Reason: OpenSSL 3.x provider architecture
# Fix: Specify provider explicitly
openssl md5 -provider legacy file.txt

# Problem: SHA-1 certificates rejected
# Reason: Stricter validation
# Fix: Reissue certificates with SHA-256+

RHEL 10 Issues

# Problem: Even stricter defaults than RHEL 9
# Impact: Legacy certificates may fail validation
# Solution: Ensure all certificates use modern algorithms
#           Check RHEL 10.x specific documentation for your minor version

8.7 Migration Impact

RHEL 7 → RHEL 8

Certificate Impact: MODERATE

  • TLS 1.0/1.1 disabled
  • Weak ciphers removed
  • certmonger integration required for automation

Action Required:

  1. Audit TLS versions in use
  2. Update cipher configurations
  3. Test applications with TLS 1.2+
  4. Consider crypto-policies

RHEL 8 → RHEL 9

Certificate Impact: HIGH

  • OpenSSL 3.x API changes
  • Legacy algorithm removal
  • Stricter certificate validation
  • Provider architecture changes

Action Required:

  1. Test all certificate operations
  2. Update custom scripts using OpenSSL
  3. Validate certificate chain integrity
  4. Check for SHA-1 usage

RHEL 9 → RHEL 10

Certificate Impact: LOW-MODERATE

  • Same OpenSSL base (3.5.5)
  • Incremental hardening
  • Policy refinements

Action Required:

  1. Review RHEL 10.x documentation
  2. Test crypto-policy compatibility
  3. Validate modern algorithm usage

8.8 Choosing the Right Approach

For Troubleshooting

# Always start with version check
cat /etc/redhat-release

# Then check OpenSSL version
openssl version

# For RHEL 8+: Check crypto-policy
update-crypto-policies --show 2>/dev/null || echo "Pre-RHEL 8"

Quick Reference Decision Tree

Is it RHEL 7?
├─ YES → Check for legacy TLS/cipher issues
│        Manual configuration likely needed
│        Consider migration planning
│
└─ NO → Is it RHEL 8?
    ├─ YES → Check crypto-policies first!
    │        Use certmonger for automation
    │        Consider RHEL 9 upgrade
    │
    └─ NO → Is it RHEL 9 or 10?
        └─ YES → Check OpenSSL 3.x provider issues
                 Verify modern algorithms in use
                 Leverage enhanced tooling

8.9 Key Takeaways

  1. Always check RHEL version first when troubleshooting
  2. RHEL 8 introduced crypto-policies - game changer for certificate management
  3. RHEL 9 uses OpenSSL 3.x - significant API and behavior changes
  4. RHEL 10 continues RHEL 9 foundation - incremental improvements
  5. Legacy algorithms progressively removed across versions
  6. Migration testing is critical - certificate behavior changes significantly

8.10 What’s Next?

Now that you understand RHEL version differences, we’ll dive deeper into:

  • Chapter 9: RHEL 7 Certificate Management (detailed)
  • Chapter 10: RHEL 8 & Crypto-Policies (detailed)
  • Chapter 11: RHEL 9 Modern Security (detailed)
  • Chapter 12: RHEL 10 Current Features (detailed)

Quick Reference Card

┌────────────────────────────────────────────────────────────┐
│ RHEL VERSION QUICK REFERENCE                               │
├─────────────┬───────────┬──────────────┬───────────────────┤
│ RHEL 7      │ 1.0.2k    │ Manual       │ Legacy-friendly   │
│ RHEL 8      │ 1.1.1k    │ Crypto-pols  │ Widely deployed   │
│ RHEL 9      │ 3.5.5     │ OpenSSL 3.x  │ Modern secure     │
│ RHEL 10     │ 3.5.5     │ Hardened     │ Future-ready      │
└─────────────┴───────────┴──────────────┴───────────────────┘

Check version:  cat /etc/redhat-release
Check OpenSSL:  openssl version
Check policy:   update-crypto-policies --show  (RHEL 8+)

Chapter Navigation

Chapter 9: RHEL 7 Certificate Management

Legacy but Important: RHEL 7 reached end of maintenance in June 2024, but many enterprises still run it. Learn how certificate management works on RHEL 7.


9.1 RHEL 7 Overview

Release: June 10, 2014 Maintenance Support Ended: June 30, 2024 Extended Life Cycle Support: Available through 2028

Key Characteristics:

  • OpenSSL Version: 1.0.2k-26 (package: openssl-1.0.2k-26.el7_9.x86_64)
  • Default TLS: TLS 1.0, 1.1, 1.2 all enabled
  • Trust Store: /etc/pki/ca-trust/extracted/
  • Management Approach: Primarily manual
  • Crypto-Policies: Not available (RHEL 8+ feature)

Note: If you’re still on RHEL 7, plan migration to RHEL 8 or 9. Security updates are limited.


9.2 OpenSSL 1.0.2k Specifics

Version Check

# Check OpenSSL version on RHEL 7
openssl version
# OpenSSL 1.0.2k-fips  12 Jan 2017

# Check package
rpm -q openssl
# openssl-1.0.2k-26.el7_9.x86_64

Key Features and Limitations

Features:

  • ✅ TLS 1.0, 1.1, 1.2 support
  • ✅ Stable and well-tested
  • ✅ Wide compatibility
  • ✅ RSA, ECC, DSA key types

Limitations:

  • ❌ No TLS 1.3 support
  • ❌ Older command syntax (genrsa vs genpkey)
  • ❌ Weaker default ciphers
  • ❌ Limited modern cipher suites

Command Syntax (RHEL 7 Style)

#============================================#
# GENERATE RSA KEY (RHEL 7)
#============================================#

# Old style (common on RHEL 7)
openssl genrsa -out server.key 2048

# With passphrase protection
openssl genrsa -aes256 -out server.key 2048

# Remove passphrase from key
openssl rsa -in server.key -out server-nopass.key


#============================================#
# GENERATE CSR (RHEL 7)
#============================================#

# Basic CSR
openssl req -new -key server.key -out server.csr

# With subject specified
openssl req -new -key server.key -out server.csr \
  -subj "/C=US/ST=State/L=City/O=Company/CN=server.example.com"

# ⚠️ Note: SANs are harder to add with RHEL 7 OpenSSL
# Need config file for SANs


#============================================#
# VIEW CERTIFICATE
#============================================#

# Full details
openssl x509 -in server.crt -noout -text

# Just expiration
openssl x509 -in server.crt -noout -dates

# Just subject
openssl x509 -in server.crt -noout -subject

9.3 Trust Store Management on RHEL 7

Adding Custom CAs

#============================================#
# ADD CUSTOM CA (RHEL 7)
#============================================#

# Step 1: Copy CA certificate to anchors directory
sudo cp corporate-ca.crt /etc/pki/ca-trust/source/anchors/

# Step 2: Update trust store
sudo update-ca-trust extract

# Step 3: Verify
trust list | grep -i "corporate"

# Verify applications use it
openssl verify -CAfile /etc/pki/tls/certs/ca-bundle.crt test-cert.crt

Trust Store Locations (RHEL 7)

/etc/pki/ca-trust/
├── source/
│   └── anchors/                   ← Add custom CAs here
│
└── extracted/
    ├── pem/
    │   └── tls-ca-bundle.pem      ← OpenSSL, Python, Ruby
    ├── openssl/
    │   └── ca-bundle.trust.crt    ← OpenSSL specific
    └── java/
        └── cacerts                ← Java applications

9.4 Service Configuration (RHEL 7 Approach)

Apache HTTPS on RHEL 7

#============================================#
# APACHE SSL/TLS SETUP (RHEL 7)
#============================================#

# Install Apache with SSL
sudo yum install httpd mod_ssl -y

# Generate certificate and key
sudo openssl genrsa -out /etc/pki/tls/private/server.key 2048
sudo openssl req -new -key /etc/pki/tls/private/server.key \
  -out /tmp/server.csr \
  -subj "/CN=$(hostname -f)"

# Get certificate from CA (or self-signed for testing)
sudo openssl x509 -req -days 365 -in /tmp/server.csr \
  -signkey /etc/pki/tls/private/server.key \
  -out /etc/pki/tls/certs/server.crt

# Configure Apache (/etc/httpd/conf.d/ssl.conf)
sudo vi /etc/httpd/conf.d/ssl.conf
# Set:
#   SSLCertificateFile /etc/pki/tls/certs/server.crt
#   SSLCertificateKeyFile /etc/pki/tls/private/server.key
#
#   # Recommended: Disable weak TLS versions
#   SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
#
#   # Recommended: Strong ciphers only
#   SSLCipherSuite HIGH:!aNULL:!MD5:!3DES:!RC4

# Start Apache
sudo systemctl enable httpd
sudo systemctl start httpd

# Test
curl -vk https://localhost/

NGINX on RHEL 7

#============================================#
# NGINX SSL/TLS SETUP (RHEL 7)
#============================================#

# Install NGINX (from EPEL)
sudo yum install epel-release -y
sudo yum install nginx -y

# Generate certificate
sudo openssl genrsa -out /etc/pki/tls/private/nginx.key 2048
sudo openssl req -new -x509 -days 365 \
  -key /etc/pki/tls/private/nginx.key \
  -out /etc/pki/tls/certs/nginx.crt \
  -subj "/CN=$(hostname -f)"

# Configure NGINX (/etc/nginx/nginx.conf)
# Add to server block:
#   listen 443 ssl;
#   ssl_certificate /etc/pki/tls/certs/nginx.crt;
#   ssl_certificate_key /etc/pki/tls/private/nginx.key;
#
#   # Recommended
#   ssl_protocols TLSv1.2;
#   ssl_ciphers HIGH:!aNULL:!MD5;

# Start NGINX
sudo systemctl enable nginx
sudo systemctl start nginx

9.5 Manual Certificate Renewal (RHEL 7)

No crypto-policies, no automatic tools - everything is manual!

Renewal Process

#============================================#
# MANUAL RENEWAL PROCESS (RHEL 7)
#============================================#

# Step 1: Check expiration (set calendar reminder)
openssl x509 -in /etc/pki/tls/certs/server.crt -noout -dates

# Step 2: Generate new CSR (reuse existing key)
openssl req -new -key /etc/pki/tls/private/server.key \
  -out /tmp/server-renewal.csr \
  -subj "/CN=server.example.com"

# Step 3: Submit CSR to CA

# Step 4: Receive new certificate from CA

# Step 5: Backup old certificate
sudo cp /etc/pki/tls/certs/server.crt \
     /etc/pki/tls/certs/server.crt.$(date +%Y%m%d).old

# Step 6: Install new certificate
sudo cp new-server.crt /etc/pki/tls/certs/server.crt
sudo chmod 644 /etc/pki/tls/certs/server.crt

# Step 7: Reload service
sudo systemctl reload httpd

# Step 8: Test
curl -v https://localhost/
openssl s_client -connect localhost:443

Tracking Certificate Renewals

#============================================#
# CREATE RENEWAL TRACKING (RHEL 7)
#============================================#

# Cron job to check expiration
cat > /etc/cron.weekly/check-cert-expiration << 'EOF'
#!/bin/bash
# Check certificates expiring in 60 days

for cert in /etc/pki/tls/certs/*.crt; do
  [ -f "$cert" ] || continue

  if ! openssl x509 -in "$cert" -noout -checkend $((86400*60)); then
    echo "⚠️ $cert expires within 60 days!"
    echo "$cert" | mail -s "Certificate Expiring Soon" admin@example.com
  fi
done
EOF

chmod +x /etc/cron.weekly/check-cert-expiration

9.6 Common RHEL 7 Certificate Issues

Issue 1: TLS 1.0/1.1 Deprecated

Problem: Modern clients reject TLS 1.0/1.1

Symptoms:

curl: (35) error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure

Fix:

# Update Apache to disable old TLS versions
# /etc/httpd/conf.d/ssl.conf
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1

# Restart Apache
sudo systemctl restart httpd

Issue 2: Weak Ciphers

Problem: PCI/Security scans flag weak ciphers

Fix:

# Apache: Use strong ciphers only
# /etc/httpd/conf.d/ssl.conf
SSLCipherSuite HIGH:!aNULL:!MD5:!3DES:!RC4:!EXPORT
SSLHonorCipherOrder on

# Test
openssl s_client -connect localhost:443 -cipher '3DES'
# Should fail if 3DES is disabled

Issue 3: Missing SANs

Problem: Modern browsers require Subject Alternative Names

RHEL 7 Challenge: SANs are harder to add with OpenSSL 1.0.2

Solution: Use config file

# Create OpenSSL config
cat > /tmp/san.cnf << EOF
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req

[req_distinguished_name]
CN = server.example.com

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = server.example.com
DNS.2 = www.example.com
IP.1 = 10.0.0.100
EOF

# Generate CSR with SANs
openssl req -new -key server.key -out server.csr -config /tmp/san.cnf

# Verify SANs in CSR
openssl req -in server.csr -noout -text | grep -A3 "Subject Alternative Name"

9.7 certmonger on RHEL 7

Available: Yes (basic version)

#============================================#
# CERTMONGER ON RHEL 7
#============================================#

# Install
sudo yum install certmonger -y
sudo systemctl enable certmonger
sudo systemctl start certmonger

# Request certificate from FreeIPA
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/web.crt \
  -k /etc/pki/tls/private/web.key \
  -K host/$(hostname -f)@REALM

# List tracked certificates
sudo getcert list

# Check specific certificate status
sudo getcert list -f /etc/pki/tls/certs/web.crt

# Monitor certmonger logs
sudo tail -f /var/log/messages | grep certmonger

RHEL 7 Limitations:

  • No ACME support (Let’s Encrypt requires manual certbot)
  • Less detailed status output
  • Fewer post-save command options

9.8 Migration Considerations

When to Migrate from RHEL 7

You should migrate if:

  • ✅ Support ended (June 2024) and you need updates
  • ✅ Need TLS 1.3 support
  • ✅ Want crypto-policies for easier management
  • ✅ Require modern security features
  • ✅ Compliance requires supported OS

Pre-Migration Certificate Tasks

#============================================#
# RHEL 7 CERTIFICATE PRE-MIGRATION AUDIT
#============================================#

# 1. List all certificates
find /etc/pki/tls/ -name "*.crt" -o -name "*.key"

# 2. Check expirations
for cert in /etc/pki/tls/certs/*.crt; do
  echo "=== $cert ==="
  openssl x509 -in "$cert" -noout -subject -dates
  echo ""
done

# 3. Check signature algorithms (SHA-1 won't work on RHEL 8+)
for cert in /etc/pki/tls/certs/*.crt; do
  SIG=$(openssl x509 -in "$cert" -noout -text | grep "Signature Algorithm" | head -2)
  echo "$cert: $SIG"
done | grep -i sha1
# If any found, reissue before migration!

# 4. Document custom CAs
ls -l /etc/pki/ca-trust/source/anchors/

# 5. Export certificates and keys
tar czf rhel7-certificates-backup-$(date +%Y%m%d).tar.gz \
  /etc/pki/tls/certs/*.crt \
  /etc/pki/tls/private/*.key \
  /etc/pki/ca-trust/source/anchors/*

9.9 Common RHEL 7 Workflows

Workflow 1: Manual Apache HTTPS Setup

# Complete workflow from scratch

# 1. Install Apache with SSL
sudo yum install httpd mod_ssl -y

# 2. Generate private key
sudo openssl genrsa -out /etc/pki/tls/private/$(hostname -s).key 2048

# 3. Set key permissions
sudo chmod 600 /etc/pki/tls/private/$(hostname -s).key

# 4. Create CSR
sudo openssl req -new \
  -key /etc/pki/tls/private/$(hostname -s).key \
  -out /tmp/$(hostname -s).csr \
  -subj "/C=US/O=Company/CN=$(hostname -f)"

# 5. Submit CSR to CA, wait for certificate

# 6. Install certificate
sudo cp $(hostname -s).crt /etc/pki/tls/certs/

# 7. Configure Apache
sudo vi /etc/httpd/conf.d/ssl.conf
# Edit:
#   SSLCertificateFile /etc/pki/tls/certs/$(hostname -s).crt
#   SSLCertificateKeyFile /etc/pki/tls/private/$(hostname -s).key
#   SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
#   SSLCipherSuite HIGH:!aNULL:!MD5:!3DES

# 8. Test configuration
sudo apachectl configtest

# 9. Open firewall
sudo firewall-cmd --add-service=https --permanent
sudo firewall-cmd --reload

# 10. Start Apache
sudo systemctl enable httpd
sudo systemctl start httpd

# 11. Test
curl -vk https://$(hostname -f)/

Workflow 2: FreeIPA Integration

#============================================#
# FREEIPA CERTIFICATE WORKFLOW (RHEL 7)
#============================================#

# Prerequisites: System must be IPA-enrolled
ipa-client-install

# Install certmonger
sudo yum install certmonger -y
sudo systemctl enable certmonger
sudo systemctl start certmonger

# Request certificate for Apache
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/$(hostname -s).crt \
  -k /etc/pki/tls/private/$(hostname -s).key \
  -K host/$(hostname -f)@REALM.EXAMPLE.COM \
  -D $(hostname -f)

# Check status
sudo getcert list

# Wait for MONITORING status (certificate issued)

# Configure Apache to use cert
# /etc/httpd/conf.d/ssl.conf

# Reload Apache when cert renews
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/$(hostname -s).crt \
  -k /etc/pki/tls/private/$(hostname -s).key \
  -K host/$(hostname -f)@REALM \
  -C "systemctl reload httpd"

9.10 Troubleshooting RHEL 7 Certificates

Diagnostic Commands

#============================================#
# RHEL 7 CERTIFICATE DIAGNOSTICS
#============================================#

# Check OpenSSL version
openssl version

# Test HTTPS locally
openssl s_client -connect localhost:443

# Check Apache SSL configuration
sudo apachectl -t -D DUMP_VHOSTS | grep 443

# View Apache SSL errors
sudo tail -f /var/log/httpd/ssl_error_log

# Check SELinux denials
sudo grep AVC /var/log/audit/audit.log | grep cert

# Check file permissions
ls -lZ /etc/pki/tls/certs/*.crt
ls -lZ /etc/pki/tls/private/*.key

# Verify certificate/key pair
openssl x509 -noout -modulus -in /etc/pki/tls/certs/server.crt | openssl md5
openssl rsa -noout -modulus -in /etc/pki/tls/private/server.key | openssl md5
# MD5 hashes should match

Common RHEL 7 Errors

ErrorCauseSolution
“certificate verify failed”Missing CA in trust storeAdd CA to /etc/pki/ca-trust/source/anchors/
“permission denied” on keyWrong permissionschmod 600 on .key file
“certificate has expired”Cert expiredRenew certificate manually
“no shared cipher”Client/server cipher mismatchUpdate SSLCipherSuite
“wrong version number”TLS version mismatchUpdate SSLProtocol

9.11 Security Hardening on RHEL 7

#============================================#
# APACHE SSL/TLS HARDENING (RHEL 7)
#============================================#

# /etc/httpd/conf.d/ssl.conf

# Disable old protocols
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1

# Strong ciphers only
SSLCipherSuite ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!3DES:!DES

# Honor server cipher preference
SSLHonorCipherOrder on

# Enable HSTS (HTTP Strict Transport Security)
Header always set Strict-Transport-Security "max-age=31536000"

# OCSP Stapling (not available in RHEL 7 OpenSSL 1.0.2 by default)
# Available in some backports

# Perfect Forward Secrecy
SSLCipherSuite ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256

9.12 Migration Path to RHEL 8+

Certificate-Specific Migration Steps

#============================================#
# PREPARE CERTIFICATES FOR MIGRATION
#============================================#

# 1. Verify all certificates use SHA-256+ (no SHA-1 or MD5)
for cert in /etc/pki/tls/certs/*.crt; do
  openssl x509 -in "$cert" -noout -text | grep "Signature Algorithm"
done | grep -i sha1 && echo "⚠️ SHA-1 certificates found! Reissue before migration!"

# 2. Verify key sizes (2048+ bits)
for cert in /etc/pki/tls/certs/*.crt; do
  SIZE=$(openssl x509 -in "$cert" -noout -text | grep "Public-Key" | grep -oP '\d+')
  if [ "$SIZE" -lt 2048 ]; then
    echo "⚠️ $cert: Key too small ($SIZE bits)"
  fi
done

# 3. Backup everything
tar czf rhel7-certs-$(hostname)-$(date +%Y%m%d).tar.gz \
  /etc/pki/tls/ \
  /etc/pki/ca-trust/source/anchors/ \
  /etc/httpd/conf.d/ssl.conf \
  /etc/nginx/nginx.conf

# 4. Document certificate inventory
./generate-cert-inventory.sh > cert-inventory-pre-migration.csv

# 5. Test TLS 1.2 compatibility
# Ensure all services work with TLS 1.2 only

9.13 When RHEL 7 Makes Sense

Still Using RHEL 7? Consider:

Reasons to Stay (Temporarily):

  • Extended Life Cycle Support contract active
  • Critical legacy applications requiring TLS 1.0/1.1
  • Migration planned for near future
  • Testing RHEL 8/9 in parallel

Reasons to Migrate:

  • ✅ Extended maintenance ended June 2024
  • ✅ No crypto-policies (harder to manage)
  • ✅ No TLS 1.3
  • ✅ Security updates limited
  • ✅ Modern applications dropping TLS 1.0/1.1 support

9.14 Key Takeaways

  1. RHEL 7 is manual - No crypto-policies, careful configuration needed
  2. OpenSSL 1.0.2k - Older syntax, no TLS 1.3
  3. TLS 1.0/1.1 enabled by default - Disable them manually
  4. SHA-1 still works - But won’t after migration to RHEL 8+
  5. certmonger available - But basic compared to RHEL 8+
  6. Plan migration - RHEL 7 support is ending
  7. Document everything - Makes migration easier

Quick Reference

┌───────────────────────────────────────────────────────┐
│ RHEL 7 CERTIFICATE QUICK REFERENCE                    │
├───────────────────────────────────────────────────────┤
│ OpenSSL:   1.0.2k-26                                  │
│ TLS:       1.0, 1.1, 1.2 (no 1.3)                     │
│ Policy:    Manual configuration (no crypto-policies)  │
│                                                       │
│ Generate:  openssl genrsa -out key.pem 2048           │
│ CSR:       openssl req -new -key key.pem -out req.csr │
│ View:      openssl x509 -in cert.crt -noout -text     │
│ Test:      openssl s_client -connect host:443         │
│                                                       │
│ Harden:    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1     │
│            SSLCipherSuite HIGH:!aNULL:!MD5:!3DES      │
└───────────────────────────────────────────────────────┘

Chapter Navigation

Chapter 10: RHEL 8 & Crypto-Policies

Game Changer: RHEL 8 introduced crypto-policies, revolutionizing how certificates and cryptography are managed system-wide. This is the most important feature to understand for RHEL 8.


10.1 What Changed in RHEL 8?

Release: May 7, 2019 Support Until: May 31, 2029 Current Version: RHEL 8.10 (as of 2024)

Major Certificate-Related Changes:

FeatureRHEL 7RHEL 8
OpenSSL1.0.2k1.1.1k-14
TLS 1.3❌ No✅ Yes
Crypto-Policies❌ NoNEW!
TLS 1.0/1.1✅ Enabled❌ Disabled (DEFAULT)
certmongerBasicEnhanced
Default SecurityMixedStronger

Package: openssl-1.1.1k-14.el8_6.x86_64


10.2 Understanding Crypto-Policies

The Revolutionary Idea

RHEL 7 Problem:

❌ Configure Apache:     SSLProtocol, SSLCipherSuite
❌ Configure NGINX:      ssl_protocols, ssl_ciphers
❌ Configure Postfix:    smtpd_tls_protocols
❌ Configure OpenLDAP:   olcTLSProtocolMin
❌ Configure every application differently!

RHEL 8 Solution:

✅ Set ONE system-wide policy
✅ All applications automatically comply!

How It Works

┌────────────────────────────────────────┐
│  update-crypto-policies --set DEFAULT  │  ← Single command
└──────────────────┬─────────────────────┘
                   │
       ┌───────────┴────────────┐
       │ Crypto-Policies System │
       └───────────┬────────────┘
                   │
    ┌──────────────┼──────────────┐
    ▼              ▼              ▼
 OpenSSL        GnuTLS         NSS
 Postfix        Apache         NGINX
 OpenSSH        Kerberos       BIND
 (all apps!)    (automatic!)   (consistent!)

10.3 Available Crypto-Policies

The Four Main Policies

# Check current policy
update-crypto-policies --show

# Policies available in RHEL 8:
PolicyTLS VersionsMin RSASHA-13DESUse Case
DEFAULT1.2, 1.32048❌ No❌ NoStandard (recommended)
LEGACY1.0+, all1024⚠️ Yes⚠️ YesOld systems compatibility
FUTURE1.2, 1.33072❌ No❌ NoStricter security
FIPS1.2, 1.32048❌ No❌ NoFederal compliance

Policy Details

DEFAULT Policy:

TLS Versions: 1.2, 1.3
Minimum RSA/DH: 2048 bits
Minimum ECC: secp256r1 (P-256)
Ciphers: AES-GCM, ChaCha20-Poly1305, AES-CBC
Signatures: SHA-256, SHA-384, SHA-512
Blocked: MD5, SHA-1 signatures, 3DES, RC4, DSS

LEGACY Policy:

TLS Versions: 1.0, 1.1, 1.2, 1.3
Minimum RSA/DH: 1024 bits
Ciphers: Includes 3DES, weak ciphers
Signatures: Allows SHA-1
Use: Only for old systems compatibility (temporary!)

FUTURE Policy:

TLS Versions: 1.2, 1.3 (stricter ciphers)
Minimum RSA/DH: 3072 bits
Minimum ECC: secp384r1 (P-384)
Signatures: SHA-384, SHA-512 preferred
Blocked: Everything in DEFAULT, plus more

FIPS Policy:

TLS Versions: 1.2, 1.3
Algorithms: Only FIPS 140-2 approved
Requires: FIPS mode enabled
Strictest: Federal compliance requirements

10.4 Changing Crypto-Policies

Basic Policy Changes

#============================================#
# VIEW CURRENT POLICY
#============================================#

update-crypto-policies --show
# DEFAULT


#============================================#
# SET POLICY
#============================================#

# Set to FUTURE (stricter)
sudo update-crypto-policies --set FUTURE

# Set to LEGACY (less secure, for compatibility)
sudo update-crypto-policies --set LEGACY

# Set to FIPS (requires FIPS mode enabled)
sudo fips-mode-setup --enable
sudo reboot
sudo update-crypto-policies --set FIPS

# Return to DEFAULT
sudo update-crypto-policies --set DEFAULT


#============================================#
# APPLY POLICY (restart services)
#============================================#

# Crypto-policies update config files, but services must restart
sudo systemctl restart httpd nginx postfix

# Or reboot (ensures everything picks up new policy)
sudo reboot

What Happens When You Change Policy

# Example: Switching to FUTURE policy

# Before:
update-crypto-policies --show
# DEFAULT

# After:
sudo update-crypto-policies --set FUTURE

# Changes happen in:
ls -l /etc/crypto-policies/back-ends/
# opensslcnf.config      ← OpenSSL config updated
# gnutls.config          ← GnuTLS config updated
# nss.config             ← NSS config updated
# bind.config            ← BIND config updated
# ... and more

# View OpenSSL policy applied:
cat /etc/crypto-policies/back-ends/opensslcnf.config

10.5 Policy Impact on Certificates

DEFAULT Policy Impact

#============================================#
# WHAT DEFAULT POLICY ALLOWS/BLOCKS
#============================================#

# ✅ ALLOWED:
- TLS 1.2, 1.3
- RSA 2048+ bits
- AES-128-GCM, AES-256-GCM
- ChaCha20-Poly1305
- SHA-256, SHA-384, SHA-512 signatures

# ❌ BLOCKED:
- TLS 1.0, 1.1
- RSA < 2048 bits
- 3DES, RC4, DES
- MD5, SHA-1 signatures
- DSA keys
- Export ciphers

Testing Against Current Policy

#============================================#
# TEST IF YOUR CERTIFICATE WORKS
#============================================#

# Test TLS 1.2
openssl s_client -connect server.example.com:443 -tls1_2

# Test TLS 1.3
openssl s_client -connect server.example.com:443 -tls1_3

# Test specific cipher
openssl s_client -connect server.example.com:443 \
  -cipher 'ECDHE-RSA-AES256-GCM-SHA384'

# See what ciphers are available under current policy
openssl ciphers -v | head -20

10.6 OpenSSL 1.1.1 Features (RHEL 8)

New Features

#============================================#
# TLS 1.3 SUPPORT (New in RHEL 8!)
#============================================#

# Test TLS 1.3
openssl s_client -connect server.example.com:443 -tls1_3

# TLS 1.3 benefits:
# - Faster handshake
# - Forward secrecy mandatory
# - Removed outdated features


#============================================#
# MODERN KEY GENERATION
#============================================#

# Old style (still works)
openssl genrsa -out server.key 2048

# New style (preferred on RHEL 8)
openssl genpkey -algorithm RSA -out server.key \
  -pkeyopt rsa_keygen_bits:2048

# EC keys (elliptic curve)
openssl genpkey -algorithm EC -out ec.key \
  -pkeyopt ec_paramgen_curve:P-256


#============================================#
# IMPROVED CSR GENERATION
#============================================#

# CSR with SANs (much easier than RHEL 7!)
openssl req -new -key server.key -out server.csr \
  -subj "/CN=server.example.com" \
  -addext "subjectAltName=DNS:server.example.com,DNS:www.example.com,IP:10.0.0.100"

# Verify SANs
openssl req -in server.csr -noout -text | grep -A2 "Subject Alternative Name"

10.7 certmonger Enhancements in RHEL 8

Improved Features

#============================================#
# CERTMONGER ON RHEL 8
#============================================#

# Better IPA integration
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/web.crt \
  -k /etc/pki/tls/private/web.key \
  -D web.example.com \
  -K host/web.example.com@REALM \
  -C "systemctl reload httpd"  # Post-save command (improved!)

# Enhanced status output
sudo getcert list -v

# Better error reporting
sudo getcert list -f /etc/pki/tls/certs/web.crt
# Shows detailed error messages if renewal fails

RHEL 8 certmonger improvements:

  • ✅ Better error messages
  • ✅ Post-save command support
  • ✅ Improved IPA integration
  • ✅ More reliable renewal

10.8 Common RHEL 8 Scenarios

Scenario 1: Migrated from RHEL 7, TLS 1.0 App Breaks

Problem:

# Application worked on RHEL 7
# After migration to RHEL 8: connection failures

Diagnosis:

# Check crypto-policy
update-crypto-policies --show
# DEFAULT  ← TLS 1.0/1.1 disabled!

# Check application logs
journalctl -xe | grep -i tls
# "wrong version number" or "no shared cipher"

Quick Fix (Temporary):

# Use LEGACY policy to allow TLS 1.0/1.1
sudo update-crypto-policies --set LEGACY
sudo systemctl restart <service>

Proper Fix:

# Update application to support TLS 1.2+
# Or configure application specifically (opt-out of policy)

Scenario 2: Need to Support Old Clients

Problem: Windows Server 2008, Java 7 clients can’t connect

Solution:

# Option 1: LEGACY policy (not recommended long-term)
sudo update-crypto-policies --set LEGACY

# Option 2: Custom policy module
# Create /etc/crypto-policies/policies/modules/COMPAT-OLD-CLIENTS.pmod
sudo update-crypto-policies --set DEFAULT:COMPAT-OLD-CLIENTS

# Option 3: Opt-out specific service
# Configure that service to allow TLS 1.0/1.1

Scenario 3: Testing Before Production

#============================================#
# TEST CRYPTO-POLICY IMPACT
#============================================#

# Current policy
CURRENT=$(update-crypto-policies --show)

# Test with FUTURE policy
sudo update-crypto-policies --set FUTURE
sudo systemctl restart httpd

# Run tests
curl https://localhost/
# Application test suite

# If problems:
sudo update-crypto-policies --set $CURRENT  # Revert
sudo systemctl restart httpd

10.9 Per-Application Overrides

When to Override

Sometimes you need ONE application to opt-out of system policy:

Example: Legacy app needs TLS 1.0, but you want DEFAULT for everything else

#============================================#
# APACHE OVERRIDE (Opt-Out)
#============================================#

# /etc/httpd/conf.d/ssl.conf
# Add this to re-enable TLS 1.0 for Apache only:
SSLProtocol all

# Or use Include to load crypto-policy
Include /etc/crypto-policies/back-ends/httpd.config
# Then override specific settings after

# ⚠️ Note: This opts OUT of crypto-policies for Apache
# You now manage Apache TLS manually again

Better: Use policy modules (see Chapter 23 for details)


10.10 Troubleshooting Crypto-Policy Issues

Common Issues

Issue 1: “no shared cipher”

# Diagnosis
update-crypto-policies --show
# DEFAULT

# Test what ciphers are available
openssl ciphers -v

# Check client request
openssl s_client -connect localhost:443 -cipher 'ALL'

# Solution: Temporarily use LEGACY to identify issue
sudo update-crypto-policies --set LEGACY
# If works → cipher compatibility issue
# Proper fix: Update client or create custom policy

Issue 2: Service fails after policy change

# Symptom
sudo systemctl status httpd
# Failed to start

# Check logs
sudo journalctl -xe -u httpd | grep -i tls

# Revert policy
sudo update-crypto-policies --set DEFAULT
sudo systemctl restart httpd

10.11 Best Practices for RHEL 8

Recommendation: Use DEFAULT Policy

# For most environments:
sudo update-crypto-policies --set DEFAULT

# Reasons:
✅ Balanced security/compatibility
✅ Tested by Red Hat
✅ Meets modern standards
✅ Blocks known weak algorithms
✅ Works with most clients

When to Use Other Policies

Use LEGACY when:

  • Temporarily supporting very old clients
  • Migration period from RHEL 7
  • Testing compatibility
  • But: Plan to move back to DEFAULT ASAP!

Use FUTURE when:

  • High-security requirements
  • All clients are modern
  • Want strictest settings
  • Planning ahead

Use FIPS when:

  • Federal compliance required
  • Government contracts
  • Regulated industries
  • Security certifications needed

10.12 Key Takeaways (RHEL 8)

  1. Crypto-policies are THE feature - Learn them well
  2. DEFAULT policy is good - Don’t change without reason
  3. TLS 1.3 now available - Faster and more secure
  4. OpenSSL 1.1.1 - Modern features, better syntax
  5. certmonger enhanced - Better automation
  6. Migration from RHEL 7 - Test thoroughly
  7. Plan for RHEL 9 - OpenSSL 3.x coming

Quick Reference

┌───────────────────────────────────────────────────────────────┐
│ RHEL 8 CRYPTO-POLICIES QUICK REFERENCE                        │
├───────────────────────────────────────────────────────────────┤
│ OpenSSL:        1.1.1k-14                                     │
│ TLS:            1.2, 1.3 (DEFAULT policy)                     │
│ Feature:        System-wide crypto-policies                   │
│                                                               │
│ View policy:    update-crypto-policies --show                 │
│ Set policy:     sudo update-crypto-policies --set <POLICY>    │
│ Policies:       DEFAULT, LEGACY, FUTURE, FIPS                 │
│                                                               │
│ Config files:   /etc/crypto-policies/back-ends/               │
│ Restart:        systemctl restart <services>                  │
│                                                               │
│ Generate key:   openssl genpkey -algorithm RSA -out key.pem   │
│ CSR with SANs:  openssl req -new -addext "subjectAltName=..." │
└───────────────────────────────────────────────────────────────┘

Chapter Navigation

Chapter 11: RHEL 9 Modern Security

Modern Standard: RHEL 9 represents the current state-of-the-art in Linux certificate management with OpenSSL 3.x, enhanced crypto-policies, and stricter security defaults.


11.1 RHEL 9 Overview

Release: May 17, 2022 Support Until: May 31, 2032 Current Version: RHEL 9.8

Major Changes from RHEL 8:

FeatureRHEL 8RHEL 9
OpenSSL1.1.1k3.5.5
ArchitectureTraditionalProvider-based
TLS 1.0/1.1LEGACY policyCompletely removed
Crypto-PoliciesBasicSubpolicies
ValidationStandardStricter
SHA-1DeprecatedBlocked
certmongerEnhancedNative IPA/tracking workflows

Package: openssl-3.5.5-2.el9_8.x86_64


11.2 OpenSSL 3.5.5 - Major Changes

Provider Architecture (New!)

What Changed: OpenSSL 3.x introduced a “provider” system for different crypto implementations.

#============================================#
# LIST PROVIDERS (RHEL 9)
#============================================#

openssl list -providers

# Output:
# Providers:
#   default
#     name: OpenSSL Default Provider
#     version: 3.5.5
#     status: active
#
#   fips
#     name: OpenSSL FIPS Provider
#     version: 3.5.5
#     status: inactive (unless FIPS mode enabled)
#
#   legacy
#     name: OpenSSL Legacy Provider
#     version: 3.5.5
#     status: inactive
#
#   base
#     name: OpenSSL Base Provider
#     version: 3.5.5
#     status: active

Legacy Algorithms Require Explicit Provider

Breaking Change: MD5, Blowfish, CAST5 need -provider legacy

#============================================#
# USING LEGACY ALGORITHMS (RHEL 9)
#============================================#

# This FAILS on RHEL 9:
openssl md5 file.txt
# Error: unsupported

# This WORKS (explicit provider):
openssl md5 -provider legacy file.txt

# Why: Legacy algorithms disabled by default for security

Modern Key Generation (RHEL 9)

#============================================#
# GENERATE KEYS (RHEL 9)
#============================================#

# RSA 2048 (standard)
openssl genpkey -algorithm RSA -out server.key \
  -pkeyopt rsa_keygen_bits:2048

# RSA 4096 (stronger)
openssl genpkey -algorithm RSA -out server.key \
  -pkeyopt rsa_keygen_bits:4096

# EC P-256 (elliptic curve, recommended)
openssl genpkey -algorithm EC -out ec.key \
  -pkeyopt ec_paramgen_curve:P-256

# EC P-384 (stronger)
openssl genpkey -algorithm EC -out ec.key \
  -pkeyopt ec_paramgen_curve:P-384


#============================================#
# GENERATE CSR WITH SANS (RHEL 9)
#============================================#

openssl req -new -key server.key -out server.csr \
  -subj "/C=US/ST=State/O=Company/CN=server.example.com" \
  -addext "subjectAltName=DNS:server.example.com,DNS:www.example.com,IP:10.0.0.100" \
  -addext "keyUsage=digitalSignature,keyEncipherment" \
  -addext "extendedKeyUsage=serverAuth,clientAuth"

# Verify
openssl req -in server.csr -noout -text | grep -A5 "Subject Alternative Name"

11.3 Enhanced Crypto-Policies (RHEL 9)

Subpolicies (New Feature!)

RHEL 9 introduces policy modifiers:

#============================================#
# CRYPTO-POLICY SUBPOLICIES (RHEL 9)
#============================================#

# Base policy with NO-SHA1 module
sudo update-crypto-policies --set DEFAULT:NO-SHA1

# Multiple modules
sudo update-crypto-policies --set DEFAULT:NO-SHA1:GOST

# Common subpolicies:
# - NO-SHA1: Completely disable SHA-1 (even in signatures)
# - NO-ENFORCE-EMS: Disable Extended Master Secret
# - GOST: Enable GOST algorithms
# - NO-CAMELLIA: Disable Camellia cipher

# View available modules
ls /usr/share/crypto-policies/policies/modules/

Custom Crypto-Policy Modules (RHEL 9)

#============================================#
# CREATE CUSTOM POLICY MODULE
#============================================#

# Create custom module
sudo vi /etc/crypto-policies/policies/modules/CUSTOM.pmod

# Example content:
min_rsa_size = 3072
min_dh_size = 3072
min_dsa_size = 3072

# Apply
sudo update-crypto-policies --set DEFAULT:CUSTOM

# Test
openssl ciphers -v | head

11.4 Stricter Certificate Validation

What’s Stricter in RHEL 9?

#============================================#
# STRICTER VALIDATION EXAMPLES
#============================================#

# 1. SHA-1 signatures completely rejected
openssl verify sha1-signed-cert.crt
# Error: CA md too weak

# 2. Self-signed without proper CA trust rejected
curl https://self-signed.example.com/
# Error: certificate verify failed

# 3. Certificate chain must be complete
# Missing intermediate → connection fails

# 4. Hostname must match (CN or SAN)
openssl s_client -connect server.example.com:443 -servername different.example.com
# Verification error: hostname mismatch

# 5. Keys < 2048 bits rejected
# (even in LEGACY policy, < 1024 rejected)

Impact on Applications

Applications compiled against OpenSSL 3.x:

  • May need code changes if using deprecated APIs
  • Error handling may be different
  • Custom crypto code needs testing

System administrators:

  • ✅ Most changes transparent
  • ✅ Commands mostly the same
  • ⚠️ Stricter validation catches more issues (this is good!)

11.5 RHEL 9 Automation: certmonger, certbot, and IdM ACME

Use the Right Client for the Right CA

#============================================#
# AUTOMATION CHOICES ON RHEL 9
#============================================#

# Native certmonger workflow for FreeIPA / IdM
sudo dnf install certmonger -y
sudo systemctl enable --now certmonger
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/internal.crt \
  -k /etc/pki/tls/private/internal.key \
  -K HTTP/$(hostname -f)@REALM \
  -D $(hostname -f) \
  -C "systemctl reload httpd"

# Public Let's Encrypt workflow
# Use certbot, not a fake certmonger Let's Encrypt CA definition.
sudo certbot certonly --apache -d web.example.com

# IdM ACME workflow (optional)
# This points at your IPA server's ACME directory, not Let's Encrypt.
sudo certbot certonly \
  --server https://ipa.example.com/acme/directory \
  -d host.example.com

Important: IdM ACME and Let’s Encrypt are different CAs. certmonger remains the native RHEL tool for IPA, local CA, and tracked renewal workflows.


11.6 Trust Store Enhancements

Advanced Trust Management

#============================================#
# RHEL 9 TRUST MANAGEMENT
#============================================#

# Add CA (same as RHEL 7/8)
sudo cp corporate-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

# NEW: Purpose-specific trust
trust anchor /path/to/ca.crt --purpose server-auth

# List trust with details
trust list --filter=ca-anchors

# Export specific CA
trust extract --format=pem-bundle --filter=ca-anchors \
  --purpose server-auth /tmp/server-cas.pem

# Remove specific trust
trust anchor --remove "pkcs11:id=%CERT_ID%"

11.7 Common RHEL 9 Issues and Solutions

Issue 1: OpenSSL 3.x API Changes

Problem: Custom application fails with OpenSSL errors

Symptoms:

Error: EVP_PKEY_RSA no longer supported
Error: Provider not available

Solution:

# Check if application is using deprecated APIs
# Application needs recompilation against OpenSSL 3.x

# Temporary: Set compat environment variable (if available)
export OPENSSL_CONF=/etc/pki/tls/openssl-compat.cnf

# Long-term: Update application

Issue 2: SHA-1 Certificates Rejected

Problem: Legacy certificates with SHA-1 signatures fail

Symptoms:

openssl verify cert.crt
# error 3: CA md too weak

Solution:

# Reissue certificate with SHA-256+
# No workaround - SHA-1 is blocked for security

# Check certificate signature
openssl x509 -in cert.crt -noout -text | grep "Signature Algorithm"
# Must show: sha256WithRSAEncryption or better

Issue 3: Legacy Algorithm Not Available

Problem: Application needs MD5/RC4/etc.

Symptoms:

openssl md5 file.txt
# Error: unsupported

Solution:

# Use legacy provider explicitly
openssl md5 -provider legacy file.txt

# For applications: Update to use SHA-256+
# Or configure to load legacy provider

11.8 FIPS Mode on RHEL 9

Improved FIPS Support

#============================================#
# FIPS MODE (RHEL 9)
#============================================#

# Enable FIPS mode
sudo fips-mode-setup --enable
sudo reboot

# Check FIPS status
fips-mode-setup --check
# FIPS mode is enabled.

# Check FIPS provider loaded
openssl list -providers | grep -A3 fips
#   fips
#     name: OpenSSL FIPS Provider
#     version: 3.5.5
#     status: active

# Generate FIPS-compliant certificate
openssl req -new -x509 -days 365 -newkey rsa:2048 \
  -keyout fips.key -out fips.crt \
  -subj "/CN=$(hostname)" -provider fips

RHEL 9 FIPS:

  • Uses OpenSSL 3.x FIPS provider
  • FIPS 140-2 validated modules
  • Transition to FIPS 140-3 in progress

11.9 Migration from RHEL 8

Certificate Impact

Moderate Impact:

  • OpenSSL API changes (affects custom apps)
  • Stricter validation (catches more issues)
  • Legacy algorithms removed
  • SHA-1 completely blocked

Pre-Migration Checks

#============================================#
# RHEL 8 → 9 CERTIFICATE PRE-MIGRATION
#============================================#

# 1. Check for SHA-1 certificates (will fail on RHEL 9)
for cert in /etc/pki/tls/certs/*.crt; do
  SIG=$(openssl x509 -in "$cert" -noout -text | grep "Signature Algorithm" | head -2)
  echo "$cert: $SIG"
done | grep -i sha1
# ⚠️ Reissue any SHA-1 certs before migration!

# 2. Check custom applications using OpenSSL
rpm -qa | grep -E "custom|local"
# Test these applications in RHEL 9 environment

# 3. Verify crypto-policy compatibility
update-crypto-policies --show

# 4. Test certificate operations
openssl s_client -connect localhost:443

# 5. Backup everything
tar czf rhel8-certs-backup-$(date +%Y%m%d).tar.gz \
  /etc/pki/tls/ \
  /etc/pki/ca-trust/source/anchors/

11.10 Best Practices for RHEL 9

#============================================#
# RECOMMENDED SETUP (RHEL 9)
#============================================#

# 1. Use DEFAULT crypto-policy (unless specific need)
sudo update-crypto-policies --set DEFAULT

# 2. Use certmonger for native automation
sudo dnf install certmonger
sudo systemctl enable --now certmonger

# 3. For public sites: Use certbot for Let's Encrypt
sudo certbot certonly --apache -d web.example.com

# 4. For internal: Use FreeIPA with certmonger
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/internal.crt \
  -k /etc/pki/tls/private/internal.key \
  -K host/$(hostname -f)@REALM

# 5. Generate EC keys (smaller, faster)
openssl genpkey -algorithm EC -out ec.key \
  -pkeyopt ec_paramgen_curve:P-256

# 6. Always use SANs
openssl req -new -addext "subjectAltName=DNS:..."

11.11 New Features You Should Use

Feature 1: Stronger certmonger + IPA Workflows

# Native RHEL automation for internal certificates
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/internal.crt \
  -k /etc/pki/tls/private/internal.key \
  -K HTTP/$(hostname -f)@REALM \
  -D $(hostname -f) \
  -C "systemctl reload httpd"

# Better status output on RHEL 9
sudo getcert list -v

Feature 2: Enhanced Status Reporting

# More detailed status
sudo getcert list -v

# Better error messages
sudo getcert list -f /etc/pki/tls/certs/web.crt
# Shows exact error reason if renewal fails

Feature 3: Crypto-Policy Subpolicies

# Fine-tune DEFAULT policy
sudo update-crypto-policies --set DEFAULT:NO-SHA1

# Multiple modifiers
sudo update-crypto-policies --set FUTURE:AD-SUPPORT

11.12 Breaking Changes from RHEL 8

API Changes

If you have custom applications:

// RHEL 8 (OpenSSL 1.1.1) - DEPRECATED on RHEL 9:
RSA *rsa = RSA_new();

// RHEL 9 (OpenSSL 3.x) - NEW API:
EVP_PKEY *pkey = EVP_PKEY_new();

Impact: Custom compiled applications may need updates

Command Changes

# Most commands work the same, but some edge cases:

# RHEL 8: This works
openssl md5 file.txt

# RHEL 9: Requires provider
openssl md5 -provider legacy file.txt

# Solution: Use SHA-256 instead
openssl sha256 file.txt

11.13 Common RHEL 9 Scenarios

Scenario 1: Fresh RHEL 9 Apache HTTPS Setup (Internal CA)

#============================================#
# COMPLETE APACHE HTTPS SETUP (RHEL 9)
#============================================#

# 1. Install Apache with mod_ssl
sudo dnf install httpd mod_ssl -y

# 2. Use certmonger + FreeIPA / IdM
sudo dnf install certmonger -y
sudo systemctl enable --now certmonger

# 3. Request certificate
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/web.crt \
  -k /etc/pki/tls/private/web.key \
  -K HTTP/$(hostname -f)@REALM \
  -D $(hostname -f) \
  -C "systemctl reload httpd"

# 4. Wait for certificate (check status)
sudo getcert list

# 5. Configure Apache to use certificate
# /etc/httpd/conf.d/ssl.conf already points to:
#   SSLCertificateFile /etc/pki/tls/certs/localhost.crt
# Update to:
#   SSLCertificateFile /etc/pki/tls/certs/web.crt
#   SSLCertificateKeyFile /etc/pki/tls/private/web.key

# 6. Crypto-policy handles TLS settings automatically!
# No need to set SSLProtocol or SSLCipherSuite

# 7. Open firewall
sudo firewall-cmd --add-service=https --permanent
sudo firewall-cmd --reload

# 8. Start Apache
sudo systemctl enable httpd
sudo systemctl start httpd

# 9. Test
curl -v https://$(hostname -f)/

# 10. Automatic renewal happens ~30 days before expiry!

Result: Fully automated internal HTTPS with FreeIPA and certmonger!


11.14 Troubleshooting RHEL 9 Certificates

Diagnostic Commands

#============================================#
# RHEL 9 CERTIFICATE DIAGNOSTICS
#============================================#

# Check OpenSSL version
openssl version
# OpenSSL 3.5.5

# Check providers
openssl list -providers

# Check crypto-policy
update-crypto-policies --show

# Test connection with TLS 1.3
openssl s_client -connect server:443 -tls1_3

# Check certificate signature algorithm
openssl x509 -in cert.crt -noout -text | grep "Signature Algorithm"
# Must be SHA-256+ on RHEL 9

# Test with legacy provider (if needed)
openssl md5 -provider legacy file.txt

# Check certmonger tracking
sudo getcert list

# View certmonger logs
sudo journalctl -u certmonger -f

Common RHEL 9 Errors

ErrorCauseSolution
“CA md too weak”SHA-1 signatureReissue with SHA-256+
“Provider not available”Legacy algorithm usedAdd -provider legacy or update to modern algorithm
“unsupported” on openssl commandAlgorithm disabledUse modern alternative or legacy provider
“no shared cipher” (migrated app)Client uses old ciphersUpdate client or use LEGACY policy temporarily
“certificate verify failed”Stricter validationCheck cert chain, SANs, expiration

11.15 When to Use RHEL 9

Ideal For:

New deployments - Start with modern security ✅ Security-focused environments - Stricter defaults ✅ Modern applications - Benefit from TLS 1.3 ✅ Long-term support - 10 years maintenance ✅ Compliance requirements - Modern security standards

Migration Timing:

From RHEL 7:

  • ✅ Yes! RHEL 7 maintenance ended June 2024
  • Plan carefully - big jump (test thoroughly)

From RHEL 8:

  • Moderate - OpenSSL 3.x is main change
  • Test custom applications first
  • SHA-1 certificates must be reissued

11.16 Key Takeaways

  1. OpenSSL 3.5.5 provider architecture - Understand providers
  2. Stricter validation - Catches security issues (good!)
  3. SHA-1 completely blocked - Reissue old certificates
  4. Crypto-policy subpolicies - Fine-tune security
  5. certmonger stays valuable for IPA and tracked renewal workflows
  6. TLS 1.3 mandatory support - Faster, more secure
  7. Plan testing - Custom apps may need updates

Quick Reference

┌──────────────────────────────────────────────────────────────┐
│ RHEL 9 CERTIFICATE QUICK REFERENCE                           │
├──────────────────────────────────────────────────────────────┤
│ OpenSSL:        3.5.5 (provider architecture)                │
│ TLS:            1.2, 1.3 (1.0/1.1 removed completely)        │
│ Feature:        Subpolicies, stricter validation             │
│                                                              │
│ Providers:      openssl list -providers                      │
│ Policy:         update-crypto-policies --show                │
│ Subpolicy:      update-crypto-policies --set DEFAULT:NO-SHA1 │
│                                                              │
│ Generate key:   openssl genpkey -algorithm RSA -out key.pem  │
│ EC key:         openssl genpkey -algorithm EC -out ec.pem    │
│                 -pkeyopt ec_paramgen_curve:P-256             │
│                                                              │
│ Public ACME:    certbot certonly --apache -d example.com     │
│ certmonger:     ipa-getcert request ...                     │
│ Legacy algo:    openssl md5 -provider legacy file.txt        │
└──────────────────────────────────────────────────────────────┘

⚠️ SHA-1 is BLOCKED - reissue old certificates!
✅ Use certmonger for automation
✅ DEFAULT policy works for most cases

Chapter Navigation

Chapter 12: RHEL 10 Current Features

Cutting Edge: RHEL 10 GA was released May 20, 2025; RHEL 10.2 is the current minor release. Learn about the latest features and prepare for the future of certificate management on Red Hat Enterprise Linux.


12.1 RHEL 10 Overview

GA Release: May 20, 2025 Current Version: RHEL 10.2 Support Until: May 31, 2035 Status: ✅ Production Release

Key Characteristics:

  • OpenSSL Version: 3.5.5-2 (package: openssl-3.5.5-2.el10_2.x86_64)
  • Same base as: RHEL 9.8 (OpenSSL 3.5.5)
  • Focus: Continued hardening, post-quantum prep, cloud-native
  • Philosophy: Incremental improvement over RHEL 9

Important: RHEL 10 features may evolve across minor versions (10.1, 10.2, etc.). Always consult official Red Hat documentation for your specific RHEL 10.x release.


12.2 What’s New vs. RHEL 9?

Key Differences

FeatureRHEL 9RHEL 10
OpenSSL3.5.53.5.5 (same base)
Crypto-PoliciesSubpoliciesEnhanced subpolicies
TLS Versions1.2, 1.31.3 preferred, 1.2 supported
FIPS140-2 modules140-3 transition
Security DefaultsStrictStricter
Container SupportGoodEnhanced
Post-QuantumFoundationActive preparation

Package: openssl-3.5.5-2.el10_2.x86_64

Not a Revolutionary Change

Unlike RHEL 7→8 (crypto-policies) or RHEL 8→9 (OpenSSL 3.x), RHEL 10 is an incremental improvement.

Think of it as:

  • RHEL 7 → 8: 🚀 Revolutionary (crypto-policies)
  • RHEL 8 → 9: 🔄 Major (OpenSSL 3.x)
  • RHEL 9 → 10: ⬆️ Incremental (refinements)

12.3 Certificate Management on RHEL 10

Same Foundation as RHEL 9

#============================================#
# RHEL 10 CERTIFICATE BASICS
#============================================#

# Same OpenSSL version as RHEL 9.8
openssl version
# OpenSSL 3.5.5 27 Jan 2026

# Same crypto-policies system
update-crypto-policies --show

# Same certmonger
getcert list

# Same directory structure
ls -la /etc/pki/tls/

Bottom Line: If you know RHEL 9, you know RHEL 10 certificates!


12.4 Enhanced Security Features

Stricter Defaults

#============================================#
# RHEL 10 SECURITY ENHANCEMENTS
#============================================#

# 1. DEFAULT policy is stricter
# - Stronger cipher preferences
# - Additional weak algorithms removed
# - Enhanced validation

# 2. LEGACY policy more restricted
# - Fewer legacy algorithms allowed
# - Stronger minimums even in LEGACY

# 3. Container certificate management improved
# - Better integration with Podman
# - Simplified cert mounting
# - Enhanced secret management

Post-Quantum Cryptography Preparation

Foundation for Future:

# RHEL 10 prepares for post-quantum algorithms
# (Not yet default, but infrastructure ready)

# Future capability (as standards finalize):
# - ML-KEM (Module-Lattice Key Encapsulation)
# - ML-DSA (Module-Lattice Digital Signatures)
# - Hybrid classical/quantum cryptography

# Current status: Monitoring NIST standards
# Expected: RHEL 10.x minor releases will add PQC support

Note: Post-quantum cryptography is still evolving. RHEL 10 provides foundation, actual implementation will come as standards are finalized.


12.5 RHEL 10-Specific Features

Feature 1: Enhanced Crypto-Policy Modules

#============================================#
# RHEL 10 CRYPTO-POLICY IMPROVEMENTS
#============================================#

# More granular control
sudo update-crypto-policies --set DEFAULT:NO-SHA1

# Better validation
update-crypto-policies --check

# Improved error messages when policies conflict

Feature 2: Improved Container Certificate Support

#============================================#
# CONTAINERS WITH CERTIFICATES (RHEL 10)
#============================================#

# Easier certificate mounting in Podman
podman run -d \
  -v /etc/pki/tls/certs/web.crt:/certs/web.crt:ro \
  -v /etc/pki/tls/private/web.key:/certs/web.key:ro \
  -p 443:443 \
  nginx

# Enhanced secret management
podman secret create web-cert /etc/pki/tls/certs/web.crt
podman secret create web-key /etc/pki/tls/private/web.key

# Use secrets in container
podman run -d --secret web-cert --secret web-key nginx

Feature 3: Enhanced FIPS Mode

#============================================#
# FIPS ON RHEL 10
#============================================#

# FIPS mode with OpenSSL 3.x FIPS provider
sudo fips-mode-setup --enable
sudo reboot

# Check FIPS status
fips-mode-setup --check

# RHEL 10: Transition toward FIPS 140-3
# Current: Still FIPS 140-2 validated modules
# Future: FIPS 140-3 compliance as certification completes

12.6 Migration from RHEL 9

Should You Upgrade?

Upgrade Considerations:

Reasons to Upgrade:

  • ✅ Want 10+ years of support (until 2035)
  • ✅ Need latest security enhancements
  • ✅ Future-proofing (post-quantum prep)
  • ✅ Enhanced container support
  • ✅ Latest features and improvements

Reasons to Wait:

  • ⏸️ RHEL 9 supported until 2032
  • ⏸️ No urgent certificate-related features
  • ⏸️ Let others test RHEL 10 in production first
  • ⏸️ Want to wait for RHEL 10.3 or 10.4

Certificate Impact: LOW

  • Same OpenSSL base (3.5.5)
  • Same tools and commands
  • Minimal breaking changes
  • Mostly transparent

Migration Process

#============================================#
# RHEL 9 → RHEL 10 CERTIFICATE MIGRATION
#============================================#

# 1. Pre-migration: Verify certificates
for cert in /etc/pki/tls/certs/*.crt; do
  openssl x509 -in "$cert" -noout -text | grep "Signature Algorithm"
done
# All should show SHA-256+ (no SHA-1 or MD5)

# 2. Backup
tar czf rhel9-certs-backup-$(date +%Y%m%d).tar.gz \
  /etc/pki/tls/ \
  /etc/pki/ca-trust/source/anchors/

# 3. Perform RHEL upgrade
sudo leapp upgrade

# 4. Verify crypto-policy
update-crypto-policies --show

# 5. Restart services
sudo systemctl restart httpd nginx postfix

# 6. Test certificates
curl -v https://localhost/
openssl s_client -connect localhost:443

# 7. Check certmonger
sudo getcert list

12.7 Best Practices for RHEL 10

#============================================#
# RHEL 10 RECOMMENDED CONFIGURATION
#============================================#

# 1. Use DEFAULT crypto-policy (already optimal)
sudo update-crypto-policies --set DEFAULT

# 2. Prefer TLS 1.3
# (Automatically preferred by DEFAULT policy)

# 3. Use EC keys for new certificates
openssl genpkey -algorithm EC -out ec.key \
  -pkeyopt ec_paramgen_curve:P-256

# 4. Automate with the right tool
# Public Let's Encrypt certificate: use certbot
sudo certbot certonly --apache -d web.example.com

# Internal FreeIPA / IdM certificate: use certmonger
# sudo ipa-getcert request \
#   -f /etc/pki/tls/certs/web.crt \
#   -k /etc/pki/tls/private/web.key \
#   -K HTTP/web.example.com@REALM \
#   -D web.example.com \
#   -C "systemctl reload httpd"

# 5. Monitor certificates
# Use built-in monitoring or external tools

# 6. Plan for future PQC
# Keep up with RHEL 10.x minor releases

12.8 Looking Ahead: Post-Quantum Readiness

What is Post-Quantum Cryptography?

Problem: Future quantum computers could break current encryption (RSA, ECC) Solution: New quantum-resistant algorithms

NIST Standards (Finalized 2024):

  • ML-KEM-768 (Key Encapsulation)
  • ML-DSA-65 (Digital Signatures)
  • SLH-DSA (Stateless signatures)

RHEL 10 Role:

  • Provides foundation for PQC
  • OpenSSL 3.x architecture supports new algorithms
  • Future RHEL 10.x releases will add PQC support

Hybrid Cryptography (Future)

# Future capability in RHEL 10.x:
# Use both classical AND quantum-resistant crypto

# Example (conceptual - not yet in RHEL 10.2):
openssl genpkey -algorithm hybrid-rsa-mlkem768 -out hybrid.key

# Provides:
# - Security against classical attacks (RSA)
# - Security against quantum attacks (ML-KEM)

Note: PQC support is coming in future RHEL 10.x minor versions as standards are finalized and tested.


12.9 What Stays the Same

No Major Breaking Changes

#============================================#
# FAMILIAR COMMANDS STILL WORK
#============================================#

# Generate key (same as RHEL 9)
openssl genpkey -algorithm RSA -out server.key \
  -pkeyopt rsa_keygen_bits:2048

# Generate CSR (same as RHEL 9)
openssl req -new -key server.key -out server.csr \
  -subj "/CN=server.example.com" \
  -addext "subjectAltName=DNS:server.example.com"

# View certificate (same)
openssl x509 -in cert.crt -noout -text

# Test connection (same)
openssl s_client -connect server:443

# Trust management (same)
sudo cp ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

# certmonger (same)
sudo getcert list

# crypto-policies (same)
update-crypto-policies --show

If you know RHEL 9, you’re ready for RHEL 10!


12.10 When to Adopt RHEL 10

Adoption Timeline Recommendations

Early Adopters (2025-2026):

  • Testing environments
  • Non-critical workloads
  • Want latest features
  • Security research

Mainstream (2026-2027):

  • New deployments
  • Refreshed infrastructure
  • After RHEL 10.3/10.4 release
  • When major apps certified

Conservative (2027-2028):

  • Critical production systems
  • Stable workloads
  • After extensive community testing
  • When migration from RHEL 9 necessary

Current Recommendation (Late 2025):

  • New projects: Consider RHEL 10
  • ⏸️ Existing RHEL 9: No urgency to upgrade
  • RHEL 8 or older: Evaluate RHEL 9 or 10
  • RHEL 7: Upgrade required (support ended)

12.11 Monitoring RHEL 10 Evolution

Stay Updated

# Check RHEL 10 minor version
cat /etc/redhat-release
# Red Hat Enterprise Linux release 10.2 (Coughlan)

# Check for updates
sudo dnf check-update

# Monitor Red Hat announcements
# - https://access.redhat.com/articles/3078
# - RHEL 10 release notes
# - Red Hat security advisories

# Subscribe to Red Hat newsletters
# Follow release notes for 10.3, 10.4, etc.

Features to Watch For

Expected in RHEL 10.x minor releases:

  • Post-quantum cryptography support
  • Additional crypto-policy enhancements
  • Further container integration
  • Enhanced automation tools
  • Additional FIPS 140-3 modules

12.12 Practical RHEL 10 Certificate Setup

Complete Example: Modern HTTPS Setup

#!/bin/bash
# Complete modern HTTPS setup on RHEL 10

echo "=== RHEL 10 Modern HTTPS Setup ==="

# 1. Install packages
sudo dnf install -y httpd mod_ssl epel-release certbot python3-certbot-apache

# 2. Enable services
sudo systemctl enable --now httpd

# 3. Request Let's Encrypt certificate with certbot
sudo certbot --apache -d $(hostname -f)

# 4. Verify the certificate
sudo certbot certificates

# 5. Update Apache configuration
# certbot usually updates Apache automatically; adjust manually only if needed
sudo sed -i "s|SSLCertificateFile.*|SSLCertificateFile /etc/letsencrypt/live/$(hostname -f)/fullchain.pem|" \
  /etc/httpd/conf.d/ssl.conf
sudo sed -i "s|SSLCertificateKeyFile.*|SSLCertificateKeyFile /etc/letsencrypt/live/$(hostname -f)/privkey.pem|" \
  /etc/httpd/conf.d/ssl.conf

# 6. Crypto-policy already optimal (DEFAULT)
update-crypto-policies --show

# 7. Open firewall
sudo firewall-cmd --add-service=https --permanent
sudo firewall-cmd --reload

# 8. Reload Apache
sudo systemctl reload httpd

# 9. Test
curl -v https://$(hostname -f)/

echo "✅ RHEL 10 modern HTTPS setup complete!"
echo "   - TLS 1.3 supported"
echo "   - Let's Encrypt certificate"
echo "   - Automatic renewal enabled"
echo "   - Optimal security (DEFAULT policy)"

12.13 Future-Proofing Strategies

Preparing for RHEL 10.x Evolution

#============================================#
# FUTURE-PROOF CERTIFICATE MANAGEMENT
#============================================#

# 1. Use modern algorithms (ready for PQC transition)
# Prefer EC over RSA
openssl genpkey -algorithm EC -out ec.key -pkeyopt ec_paramgen_curve:P-256

# 2. Keep certificates short-lived (90 days or less)
# Easier to rotate when algorithms change

# 3. Automate everything
# Use certbot for public ACME and certmonger for IPA/internal workflows

# 4. Monitor Red Hat announcements
# Subscribe to security and release notifications

# 5. Test PQC when available
# Be early tester of new features in RHEL 10.x

# 6. Document your setup
# Makes future transitions easier

12.14 Known Issues and Workarounds

Issue 1: Same as RHEL 9 (OpenSSL 3.x)

Most RHEL 9 issues apply to RHEL 10:

  • Legacy algorithms need -provider legacy
  • SHA-1 blocked
  • Custom apps may need OpenSSL 3.x updates

Reference: See Chapter 11 for OpenSSL 3.x issues

Issue 2: Even Stricter Validation

RHEL 10 may catch issues RHEL 9 allowed:

# Example: Marginal certificate that worked on RHEL 9
# might fail on RHEL 10

# Solution: Always use best practices
# - SHA-256+ signatures
# - 2048+ bit keys (4096 recommended)
# - Proper SANs
# - Valid trust chains

12.15 When to Choose RHEL 10

Decision Matrix

ScenarioRHEL 9RHEL 10Recommendation
New deployment 2025+✅ Good✅ BetterRHEL 10
Existing RHEL 9✅ Keep⏸️ WaitStay on 9 for now
Migrating from RHEL 8✅ Yes✅ ConsiderEither (9 is safer)
Migrating from RHEL 7✅ Yes⚠️ Big jumpGo to 9 first
10+ year horizon⏸️ 2032 support✅ 2035 supportRHEL 10
Cutting edge security✅ Good✅ BetterRHEL 10
Production critical✅ Proven⏸️ NewerRHEL 9 (safer)

12.16 Key Takeaways

  1. RHEL 10 = RHEL 9 + incremental improvements
  2. Same OpenSSL 3.5.5 base - No major API changes
  3. Stricter security defaults - Good for security
  4. Post-quantum preparation - Future-ready infrastructure
  5. No urgent certificate changes - Transition is smooth
  6. RHEL 9 knowledge transfers - Same tools and commands
  7. Watch for minor releases - 10.3, 10.4 may add features

12.17 Troubleshooting RHEL 10

Diagnostic Approach

#============================================#
# RHEL 10 CERTIFICATE TROUBLESHOOTING
#============================================#

# Use troubleshooting methodology (Chapter 27) and RHEL 9 patterns (Chapter 11)

# 1. Verify RHEL 10 version
cat /etc/redhat-release
# Red Hat Enterprise Linux release 10.2 (Coughlan)

# 2. Check OpenSSL
openssl version
# OpenSSL 3.5.5

# 3. Check crypto-policy
update-crypto-policies --show

# 4. Test certificate
openssl x509 -in cert.crt -noout -text

# 5. Test connection
openssl s_client -connect server:443 -tls1_3

# 6. Check providers (if issues)
openssl list -providers

# 7. Check logs
sudo journalctl -xe | grep -i cert

No new troubleshooting techniques needed - same as RHEL 9!


From RHEL 9 to RHEL 10

#============================================#
# CERTIFICATE-SAFE RHEL 9→10 MIGRATION
#============================================#

# Phase 1: Preparation
# - Backup all certificates
# - Document current configuration
# - Test in lab environment

# Phase 2: Migration
# - Use standard RHEL upgrade process
# - Certificates should transfer seamlessly

# Phase 3: Verification
# - Verify crypto-policy unchanged
# - Test all certificate operations
# - Confirm certmonger tracking maintained
# - Test services using certificates

# Phase 4: Optimization
# - Consider EC keys for new certificates
# - Review and update crypto-policy if needed
# - Monitor for RHEL 10.x enhancements

12.19 Documentation and Resources

Official Resources

## RHEL 10 Certificate Resources

### Official Documentation
- RHEL 10 Release Notes (check for your specific 10.x version)
- https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/10

### Security Updates
- https://access.redhat.com/security/
- Subscribe to RHEL security announcements

### Crypto-Policies
- https://access.redhat.com/articles/3642912
- Check for RHEL 10-specific updates

### Support
- Red Hat Customer Portal
- Red Hat Support Cases
- RHEL community forums

12.20 Quick Reference

┌──────────────────────────────────────────────────────────────┐
│ RHEL 10 CERTIFICATE QUICK REFERENCE                          │
├──────────────────────────────────────────────────────────────┤
│ OpenSSL:      3.5.5-2 (same as RHEL 9.8)                     │
│ TLS:          1.3 preferred, 1.2 supported                   │
│ GA Release:   May 20, 2025 (RHEL 10.0)                       │
│ Status:       Production ready                               │
│                                                              │
│ Key Change:   Incremental security improvements              │
│ Migration:    Low impact from RHEL 9                         │
│ Commands:     Same as RHEL 9                                 │
│ Tools:        Same as RHEL 9                                 │
│                                                              │
│ Future:       Post-quantum crypto preparation                │
│               Watch for features in 10.3, 10.4+              │
│                                                              │
│ Verify:       cat /etc/redhat-release                        │
│               openssl version                                │
│               update-crypto-policies --show                  │
└──────────────────────────────────────────────────────────────┘

✅ If you know RHEL 9 certificates, you know RHEL 10!
⚠️ Always check official docs for your specific 10.x minor version

Chapter Navigation

Chapter 13: Cross-Version Compatibility

Real-World Challenge: Your environment probably has RHEL 7, 8, and 9 systems all talking to each other. How do you make certificates work across all versions?


13.1 The Mixed Environment Reality

Most enterprises don’t upgrade everything at once. You’ll encounter:

Production Environment (Typical):
├── Legacy App Server (RHEL 7)
├── Database Server (RHEL 8)
├── Web Tier (RHEL 9)
├── Management Node (RHEL 10)
└── Clients: Windows, Mac, Linux, Mobile

Challenge: These systems have different:

  • TLS version support
  • Cipher suites
  • Certificate validation rules
  • OpenSSL versions
  • Crypto-policies (or lack thereof)

13.2 Common Compatibility Issues

Issue 1: TLS Version Mismatches

Scenario: RHEL 9 server (TLS 1.2+ only) ← RHEL 7 client (TLS 1.0/1.1 default)

# RHEL 7 client trying to connect to RHEL 9 server
curl https://rhel9-server.example.com/
# Error: SSL routines:ssl3_get_record:wrong version number

# Why: RHEL 7 tries TLS 1.0 first, RHEL 9 rejects it

Solution:

# Option 1: Update RHEL 7 client to use TLS 1.2
# Edit /etc/httpd/conf.d/ssl.conf (if Apache client)
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1

# Option 2: Temporary LEGACY policy on RHEL 9 (NOT recommended)
# sudo update-crypto-policies --set LEGACY  # On RHEL 9 server

# Option 3: Best - Upgrade RHEL 7 systems!

Issue 2: Cipher Suite Mismatches

Scenario: Modern server doesn’t support old client ciphers

# RHEL 7 client → RHEL 9 server
openssl s_client -connect rhel9-server:443 -cipher '3DES'
# Error: no shared cipher

Why: 3DES is blocked in RHEL 8+ DEFAULT policy

Solution:

# Check what ciphers are available
openssl ciphers -v 'HIGH:!aNULL:!MD5' | head

# Test specific cipher on client
openssl s_client -connect server:443 -cipher 'AES256-GCM-SHA384'

# On RHEL 9 server, if you MUST support old clients:
sudo update-crypto-policies --set LEGACY  # Temporary!

Issue 3: Certificate Validation Differences

Scenario: SHA-1 signed certificate

Certificate with SHA-1 signature:
├── ✅ Works on RHEL 7
├── ❌ Rejected by RHEL 8 DEFAULT
├── ❌ Rejected by RHEL 9
└── ❌ Rejected by RHEL 10

Solution:

# Reissue certificate with SHA-256 or better
openssl req -new -key server.key -out server.csr -sha256

# Verify signature algorithm
openssl x509 -in cert.crt -noout -text | grep "Signature Algorithm"
# Should show: sha256WithRSAEncryption (or better)

13.3 Compatibility Matrix

Client → Server Compatibility

Client ↓ Server →RHEL 7 ServerRHEL 8 Server (DEFAULT)RHEL 9 Server (DEFAULT)RHEL 10 Server
RHEL 7 Client✅ Full⚠️ TLS 1.0/1.1 issue⚠️ TLS 1.0/1.1 issue⚠️ TLS 1.0/1.1 issue
RHEL 8 Client✅ Full✅ Full✅ Full✅ Full
RHEL 9 Client⚠️ Weak cipher warning✅ Full✅ Full✅ Full
RHEL 10 Client⚠️ Weak cipher warning✅ Full✅ Full✅ Full
Windows 10✅ Full✅ Full✅ Full✅ Full
Windows Server 2012✅ Full⚠️ May need TLS 1.0/1.1⚠️ May need TLS 1.0/1.1⚠️ May need TLS 1.0/1.1
Old Java 7✅ Full❌ No TLS 1.2❌ No TLS 1.2❌ No TLS 1.2

Legend:

  • ✅ Works without changes
  • ⚠️ Works with configuration changes
  • ❌ Incompatible without major updates

13.4 Certificate Requirements for Maximum Compatibility

The “Universal” Certificate Profile

To work across all RHEL versions (7-10) and external clients:

# Certificate Requirements:
✅ RSA key: 2048 bits minimum (4096 for future-proofing)
✅ Signature: SHA-256 or better (not SHA-1!)
✅ Subject Alternative Names (SANs) required
✅ Validity: ≤ 365 days (browser requirement)
✅ Key Usage: Proper extensions set

❌ Avoid: SHA-1 signatures
❌ Avoid: RSA < 2048 bits
❌ Avoid: Missing SANs
❌ Avoid: CN-only certificates

Generate a Compatible Certificate

#============================================#
# STEP 1: Generate Key (works on all RHEL versions)
#============================================#

# RSA 2048 (minimum, compatible)
openssl genpkey -algorithm RSA -out universal.key -pkeyopt rsa_keygen_bits:2048

# Or RSA 4096 (better, still compatible)
openssl genpkey -algorithm RSA -out universal.key -pkeyopt rsa_keygen_bits:4096


#============================================#
# STEP 2: Create CSR with SANs
#============================================#

openssl req -new -key universal.key -out universal.csr \
  -subj "/C=US/ST=State/L=City/O=Company/CN=server.example.com" \
  -addext "subjectAltName=DNS:server.example.com,DNS:www.example.com,IP:10.0.0.100" \
  -addext "keyUsage=digitalSignature,keyEncipherment" \
  -addext "extendedKeyUsage=serverAuth,clientAuth"


#============================================#
# STEP 3: Verify CSR
#============================================#

openssl req -in universal.csr -noout -text | grep -A2 "Subject Alternative Name"
# Should show your SANs

openssl req -in universal.csr -noout -text | grep "Public-Key"
# Should show: Public-Key: (2048 bit) or higher

13.5 Testing Cross-Version Compatibility

Test Suite Script

#!/bin/bash
# test-cert-compatibility.sh
# Tests if certificate works from various RHEL versions

SERVER_HOST="server.example.com"
SERVER_PORT="443"
CERT_FILE="/etc/pki/tls/certs/server.crt"

echo "=== Certificate Compatibility Test Suite ==="
echo ""

#============================================#
# TEST 1: Certificate Properties
#============================================#

echo "1. Certificate Properties:"
echo "   Signature Algorithm:"
openssl x509 -in "$CERT_FILE" -noout -text | grep "Signature Algorithm" | head -1

echo "   Key Size:"
openssl x509 -in "$CERT_FILE" -noout -text | grep "Public-Key"

echo "   SANs:"
openssl x509 -in "$CERT_FILE" -noout -ext subjectAltName 2>/dev/null || echo "   No SANs found!"

echo ""


#============================================#
# TEST 2: TLS Version Support
#============================================#

echo "2. TLS Version Support:"

for version in tls1 tls1_1 tls1_2 tls1_3; do
  if openssl s_client -connect "$SERVER_HOST:$SERVER_PORT" -"$version" </dev/null 2>&1 | grep -q "Cipher"; then
    echo "   ${version//_/.}: ✅ Supported"
  else
    echo "   ${version//_/.}: ❌ Not supported"
  fi
done

echo ""


#============================================#
# TEST 3: Cipher Suite Compatibility
#============================================#

echo "3. Common Cipher Tests:"

# Modern cipher (RHEL 8+)
if openssl s_client -connect "$SERVER_HOST:$SERVER_PORT" -cipher 'ECDHE-RSA-AES256-GCM-SHA384' </dev/null 2>&1 | grep -q "Cipher"; then
  echo "   Modern cipher (ECDHE-RSA-AES256-GCM-SHA384): ✅"
else
  echo "   Modern cipher: ❌"
fi

# Legacy cipher (RHEL 7)
if openssl s_client -connect "$SERVER_HOST:$SERVER_PORT" -cipher 'AES256-SHA' </dev/null 2>&1 | grep -q "Cipher"; then
  echo "   Legacy cipher (AES256-SHA): ✅ (may indicate LEGACY policy)"
else
  echo "   Legacy cipher: ❌ (good for security)"
fi


#============================================#
# TEST 4: Certificate Validation
#============================================#

echo ""
echo "4. Certificate Validation:"

if openssl verify -CAfile /etc/pki/tls/certs/ca-bundle.crt "$CERT_FILE" | grep -q "OK"; then
  echo "   Trust chain: ✅ Valid"
else
  echo "   Trust chain: ❌ Invalid"
fi

echo ""
echo "=== Test Complete ==="

Usage:

chmod +x test-cert-compatibility.sh
sudo ./test-cert-compatibility.sh

13.6 Handling Specific Compatibility Scenarios

Scenario 1: RHEL 7 Client → RHEL 9 Server

Problem: Connection fails with TLS version error

Client-Side Fix (RHEL 7):

# For curl
curl --tlsv1.2 https://rhel9-server/

# For wget
wget --secure-protocol=TLSv1_2 https://rhel9-server/

# For applications using OpenSSL, set environment variable
export OPENSSL_CONF=/etc/pki/tls/openssl-tls12.cnf

# Create custom config
cat > /etc/pki/tls/openssl-tls12.cnf << 'EOF'
openssl_conf = default_conf

[default_conf]
ssl_conf = ssl_sect

[ssl_sect]
system_default = system_default_sect

[system_default_sect]
MinProtocol = TLSv1.2
CipherString = DEFAULT@SECLEVEL=1
EOF

Server-Side Fix (RHEL 9) - NOT RECOMMENDED:

# Only if absolutely necessary and temporarily!
sudo update-crypto-policies --set LEGACY
sudo systemctl restart httpd  # Or your service

Scenario 2: Mixed CA Trust

Problem: Corporate CA trusted on some systems but not others

Solution: Consistent trust store across all versions

#============================================#
# DEPLOYMENT SCRIPT (run on all RHEL versions)
#============================================#

#!/bin/bash
# deploy-corporate-ca.sh

CA_CERT_URL="http://pki.example.com/ca-chain.crt"
CA_CERT_FILE="/etc/pki/ca-trust/source/anchors/corporate-ca-chain.crt"

# Download CA certificate
curl -o "$CA_CERT_FILE" "$CA_CERT_URL"

# Update trust store (works on all RHEL versions)
update-ca-trust extract

# Verify
if trust list | grep -q "Corporate Root CA"; then
  echo "✅ Corporate CA installed successfully"
else
  echo "❌ Corporate CA installation failed"
  exit 1
fi

Scenario 3: Application Using Old TLS Library

Problem: Java 7 application can’t connect to modern servers

Check Java TLS Support:

# Check Java version
java -version

# Test TLS support
java -Djavax.net.debug=ssl:handshake -jar app.jar 2>&1 | grep "TLS"

Options:

# Option 1: Upgrade Java (best)
sudo dnf install java-11-openjdk

# Option 2: Enable TLS 1.2 in Java 7 (if upgrade impossible)
# Add to Java startup:
-Dhttps.protocols=TLSv1.2

# Option 3: Use wrapper script
#!/bin/bash
export JAVA_OPTS="-Dhttps.protocols=TLSv1.2 -Djavax.net.ssl.trustStore=/etc/pki/java/cacerts"
java $JAVA_OPTS -jar /path/to/app.jar

13.7 Crypto-Policy Compatibility

Understanding Policy Impact Across Versions

#============================================#
# RHEL 7 (No crypto-policies)
#============================================#

# Manual configuration in each app
# Apache: /etc/httpd/conf.d/ssl.conf
# NGINX: /etc/nginx/nginx.conf
# Postfix: /etc/postfix/main.cf


#============================================#
# RHEL 8/9/10 (crypto-policies)
#============================================#

# System-wide control
update-crypto-policies --set DEFAULT

# To support RHEL 7 clients, might need:
update-crypto-policies --set LEGACY  # Temporarily!

Policy Equivalents for Mixed Environments

If you need to maintain compatibility:

Option A: Use LEGACY on modern systems (not recommended long-term)

# On RHEL 8/9/10 servers
sudo update-crypto-policies --set LEGACY

Option B: Configure RHEL 7 to match DEFAULT (recommended)

# On RHEL 7, manually configure to match RHEL 8+ DEFAULT
# Apache example:
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite HIGH:!aNULL:!MD5:!3DES:!CAMELLIA
SSLHonorCipherOrder on

13.8 Migration Path: Gradual Upgrade

Phase 1: Prepare RHEL 7 (Pre-Migration)

# 1. Reissue all certificates with SHA-256+
# 2. Test TLS 1.2 compatibility
# 3. Update cipher configurations
# 4. Document current certificate inventory

Phase 2: Deploy RHEL 8 (Transition)

# 1. Start with LEGACY policy
sudo update-crypto-policies --set LEGACY

# 2. Deploy services
# 3. Test thoroughly
# 4. Gradually switch to DEFAULT
sudo update-crypto-policies --set DEFAULT

Phase 3: Upgrade to RHEL 9 (Modernization)

# 1. All clients should be RHEL 8+ or TLS 1.2 capable
# 2. Use DEFAULT policy
# 3. Monitor for compatibility issues
# 4. Consider FUTURE policy after stabilization

13.9 Troubleshooting Cross-Version Issues

Diagnostic Commands

#============================================#
# ON CLIENT
#============================================#

# Test specific TLS version
openssl s_client -connect server:443 -tls1_2

# Test with verbose output
curl -v --tlsv1.2 https://server/

# Check client OpenSSL
openssl version
openssl ciphers -v


#============================================#
# ON SERVER
#============================================#

# Check crypto-policy (RHEL 8+)
update-crypto-policies --show

# Check OpenSSL config
openssl version
cat /etc/crypto-policies/back-ends/opensslcnf.config

# Test server certificate
openssl s_client -connect localhost:443 -servername $(hostname)

# Check service logs
sudo journalctl -xe | grep -i tls
sudo tail -f /var/log/httpd/ssl_error_log

Common Error Messages

ErrorCauseSolution
“wrong version number”TLS version mismatchUpdate client to TLS 1.2+
“no shared cipher”Cipher incompatibilityCheck crypto-policy or cipher config
“certificate verify failed”Trust or validation issueCheck CA trust, certificate validity
“sslv3 alert handshake failure”Protocol incompatibilityUpdate TLS versions
“unsafe legacy renegotiation”Old OpenSSL on clientUpdate client OpenSSL

13.10 Best Practices for Mixed Environments

1. Standardize Certificate Issuance

# Certificate Standard (example)
Algorithm: RSA
Key Size: 2048 bits minimum (4096 preferred)
Signature: SHA-256 or better
Validity: 365 days maximum
SANs: Always include
Extensions: Proper key usage set

2. Maintain Consistent Trust Stores

# Deploy script for all systems
for host in rhel7-hosts rhel8-hosts rhel9-hosts; do
  ssh "$host" 'sudo cp /path/to/ca.crt /etc/pki/ca-trust/source/anchors/ && sudo update-ca-trust'
done

3. Test Before Deploying

# Test matrix
RHEL 7 client → RHEL 7 server ✓
RHEL 7 client → RHEL 8 server ✓
RHEL 7 client → RHEL 9 server ✓
RHEL 8 client → RHEL 7 server ✓
RHEL 8 client → RHEL 9 server ✓
RHEL 9 client → RHEL 8 server ✓

4. Document Your Environment

## Certificate Compatibility Matrix

### Servers:
- App Server 1: RHEL 7.9, TLS 1.0-1.2, RSA 2048
- Database: RHEL 8.10, DEFAULT policy, RSA 2048
- Web Tier: RHEL 9.8, DEFAULT policy, RSA 4096

### Known Limitations:
- RHEL 7 systems require TLS 1.0/1.1 for legacy app X
- Database requires specific cipher: AES256-GCM-SHA384

### Upgrade Plan:
- Q1 2025: Migrate App Server 1 to RHEL 8
- Q2 2025: Update all certificates to RSA 4096

13.11 Key Takeaways

  1. Mixed environments are normal - Plan for compatibility
  2. TLS 1.2+ is the minimum for modern systems
  3. SHA-256+ signatures required for RHEL 8+
  4. Crypto-policies changed everything (RHEL 8+)
  5. Test across all versions before deploying
  6. Document everything - especially exceptions
  7. Upgrade path is gradual - don’t rush, test thoroughly

Quick Reference

┌──────────────────────────────────────────────────────────────┐
│ CROSS-VERSION COMPATIBILITY CHECKLIST                        │
├──────────────────────────────────────────────────────────────┤
│ ✅ RSA 2048+ bits                                            │
│ ✅ SHA-256+ signature                                        │
│ ✅ SANs included                                             │
│ ✅ TLS 1.2+ support                                          │
│ ✅ Modern ciphers                                            │
│ ✅ Consistent CA trust                                       │
│ ✅ Tested across all versions                                │
└──────────────────────────────────────────────────────────────┘

Test command:
openssl s_client -connect server:443 -tls1_2 -servername server

Check policy (RHEL 8+):
update-crypto-policies --show

Chapter Navigation

Chapter 14: Apache httpd on RHEL

Most Common: Apache (httpd) is the most widely deployed web server on RHEL. Master Apache HTTPS configuration across all RHEL versions.


14.1 Apache on RHEL Overview

Package Name: httpd SSL/TLS Module: mod_ssl Config Location: /etc/httpd/conf.d/ssl.conf Certificate Path: /etc/pki/tls/certs/ Key Path: /etc/pki/tls/private/

Version Comparison

RHEL VersionApache VersionOpenSSLConfig Approach
RHEL 72.4.61.0.2kManual SSL configuration
RHEL 82.4.37+1.1.1kManual + crypto-policies
RHEL 92.4.53+3.5.5Crypto-policies preferred
RHEL 102.4.62+3.5.5Crypto-policies optimal

14.2 Installation

RHEL 7

#============================================#
# INSTALL APACHE WITH SSL (RHEL 7)
#============================================#

sudo yum install httpd mod_ssl -y
sudo systemctl enable httpd
sudo systemctl start httpd

# Open firewall
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

# Verify
systemctl status httpd

RHEL 8/9/10

#============================================#
# INSTALL APACHE WITH SSL (RHEL 8/9/10)
#============================================#

sudo dnf install httpd mod_ssl -y
sudo systemctl enable httpd
sudo systemctl start httpd

# Open firewall
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

# Verify
systemctl status httpd
ss -tlnp | grep :443  # Check if listening on 443

14.3 Basic SSL Configuration

Default SSL Configuration

# Main SSL config file
/etc/httpd/conf.d/ssl.conf

# Key directives:
SSLEngine on
SSLCertificateFile /etc/pki/tls/certs/localhost.crt
SSLCertificateKeyFile /etc/pki/tls/private/localhost.key

Complete Virtual Host Example

#============================================#
# /etc/httpd/conf.d/ssl.conf
# Or /etc/httpd/conf.d/mysite-ssl.conf
#============================================#

<VirtualHost *:443>
    ServerName www.example.com
    ServerAlias example.com
    DocumentRoot /var/www/html

    # Enable SSL/TLS
    SSLEngine on

    # Certificate files
    SSLCertificateFile      /etc/pki/tls/certs/www.example.com.crt
    SSLCertificateKeyFile   /etc/pki/tls/private/www.example.com.key
    SSLCertificateChainFile /etc/pki/tls/certs/chain.crt

    # TLS protocols (RHEL 7 - manual config)
    SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1

    # Cipher suite (RHEL 7 - manual)
    SSLCipherSuite          HIGH:!aNULL:!MD5:!3DES:!RC4
    SSLHonorCipherOrder     on

    # HSTS (recommended)
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"

    # Logging
    ErrorLog  /var/log/httpd/ssl_error_log
    CustomLog /var/log/httpd/ssl_access_log combined
</VirtualHost>

14.4 Version-Specific Configuration

RHEL 7: Manual SSL Configuration

#============================================#
# APACHE SSL - RHEL 7 BEST PRACTICES
#============================================#

<VirtualHost *:443>
    ServerName www.example.com
    SSLEngine on

    # Certificates
    SSLCertificateFile      /etc/pki/tls/certs/www.crt
    SSLCertificateKeyFile   /etc/pki/tls/private/www.key
    SSLCertificateChainFile /etc/pki/tls/certs/chain.crt

    # REQUIRED: Disable old TLS versions manually
    SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1

    # REQUIRED: Strong ciphers only
    SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
    SSLHonorCipherOrder on

    # Security headers
    Header always set Strict-Transport-Security "max-age=31536000"
    Header always set X-Frame-Options DENY
    Header always set X-Content-Type-Options nosniff
</VirtualHost>

RHEL 8/9/10: Crypto-Policies Integrated

#============================================#
# APACHE SSL - RHEL 8/9/10 WITH CRYPTO-POLICIES
#============================================#

<VirtualHost *:443>
    ServerName www.example.com
    SSLEngine on

    # Certificates
    SSLCertificateFile      /etc/pki/tls/certs/www.crt
    SSLCertificateKeyFile   /etc/pki/tls/private/www.key
    SSLCertificateChainFile /etc/pki/tls/certs/chain.crt

    # NO NEED to set SSLProtocol or SSLCipherSuite!
    # Crypto-policies handles it automatically
    # (unless you have specific requirements)

    # Security headers (still manual)
    Header always set Strict-Transport-Security "max-age=31536000"
    Header always set X-Frame-Options DENY
    Header always set X-Content-Type-Options nosniff
</VirtualHost>

Key Difference: On RHEL 8+, crypto-policies automatically configures TLS versions and ciphers!

Checking Crypto-Policy Integration

#============================================#
# VERIFY CRYPTO-POLICY (RHEL 8/9/10)
#============================================#

# Check current policy
update-crypto-policies --show

# View Apache-specific policy
cat /etc/crypto-policies/back-ends/httpd.config

# Apache automatically includes this
grep -r "crypto-policies" /etc/httpd/

14.5 Certificate Generation for Apache

Complete Workflow

#============================================#
# GENERATE CERTIFICATE FOR APACHE (ALL VERSIONS)
#============================================#

# Step 1: Generate private key
sudo openssl genpkey -algorithm RSA \
  -out /etc/pki/tls/private/www.example.com.key \
  -pkeyopt rsa_keygen_bits:2048

# Step 2: Set permissions
sudo chmod 600 /etc/pki/tls/private/www.example.com.key
sudo chown root:root /etc/pki/tls/private/www.example.com.key

# Step 3: Generate CSR with SANs
sudo openssl req -new \
  -key /etc/pki/tls/private/www.example.com.key \
  -out /tmp/www.example.com.csr \
  -subj "/C=US/ST=State/L=City/O=Company/CN=www.example.com" \
  -addext "subjectAltName=DNS:www.example.com,DNS:example.com"

# Step 4: Submit CSR to CA, receive certificate

# Step 5: Install certificate
sudo cp www.example.com.crt /etc/pki/tls/certs/
sudo chmod 644 /etc/pki/tls/certs/www.example.com.crt

# Step 6: If using intermediate certificates, install chain
sudo cp chain.crt /etc/pki/tls/certs/www.example.com-chain.crt

# Step 7: Update Apache config (see section 13.3)

# Step 8: Test configuration
sudo apachectl configtest

# Step 9: Reload Apache
sudo systemctl reload httpd

# Step 10: Test HTTPS
curl -v https://www.example.com/
openssl s_client -connect www.example.com:443 -servername www.example.com

14.6 certmonger Integration (Automation!)

Using certmonger with Apache

#============================================#
# AUTOMATE APACHE CERTIFICATES WITH CERTMONGER
#============================================#

# Install certmonger
# RHEL 8/9/10
sudo dnf install certmonger

# RHEL 7
# sudo yum install certmonger

sudo systemctl enable --now certmonger

# FreeIPA / internal CA workflow
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/www.example.com.crt \
  -k /etc/pki/tls/private/www.example.com.key \
  -D www.example.com \
  -K host/www.example.com@REALM \
  -C "systemctl reload httpd"  # Auto-reload Apache after renewal!

# Check status
sudo getcert list

# For public Let's Encrypt certificates, use certbot in section 14.7.

Benefits:

  • ✅ Automatic renewal
  • ✅ No downtime (reload, not restart)
  • ✅ Tracks expiration
  • ✅ Email alerts on failure

14.7 Let’s Encrypt with certbot

⚠️ IMPORTANT: EPEL Required

certbot is NOT available in official RHEL repositories. It requires EPEL (Extra Packages for Enterprise Linux), a community-maintained repository.

For production RHEL environments, consider:

  • FreeIPA with certmonger (recommended for RHEL)
  • Manual certificate management
  • Commercial CA with certmonger
#============================================#
# CERTBOT SETUP (REQUIRES EPEL!)
#============================================#

# Step 1: Enable EPEL
# RHEL 7:
# sudo yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -y

# RHEL 8:
# sudo dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm -y

# RHEL 9/10:
sudo dnf install epel-release -y

# Step 2: Install certbot
# RHEL 7:
# sudo yum install certbot python2-certbot-apache -y

# RHEL 8/9/10:
sudo dnf install certbot python3-certbot-apache -y

# Step 3: Obtain certificate (automated!)
sudo certbot --apache -d www.example.com -d example.com

# Step 4: Certbot automatically:
#  - Generates certificate
#  - Configures Apache
#  - Sets up renewal timer
#  - Enables HTTPS redirect

# Step 5: Test automatic renewal
sudo certbot renew --dry-run

# Check renewal timer
systemctl list-timers | grep certbot

Pros:

  • ✅ Fully automated
  • ✅ Free certificates
  • ✅ Apache config handled automatically

Cons:

  • ❌ Requires EPEL (not officially supported by Red Hat)
  • ❌ External dependency (Let’s Encrypt)
  • ⚠️ Domain must be publicly accessible

14.8 Troubleshooting Apache HTTPS

Common Issues Checklist

#============================================#
# APACHE SSL TROUBLESHOOTING CHECKLIST
#============================================#

# 1. Check if mod_ssl is loaded
sudo httpd -M | grep ssl_module
# Should show: ssl_module (shared)

# 2. Check configuration syntax
sudo apachectl configtest
# Should show: Syntax OK

# 3. Check certificate files exist
ls -l /etc/pki/tls/certs/www.crt
ls -l /etc/pki/tls/private/www.key

# 4. Verify permissions
ls -l /etc/pki/tls/private/www.key
# Should be: -rw------- (600)

# 5. Check SELinux context
ls -Z /etc/pki/tls/certs/www.crt
ls -Z /etc/pki/tls/private/www.key
# Should show: cert_t

# 6. Test certificate/key pair match
CERT_MOD=$(openssl x509 -noout -modulus -in /etc/pki/tls/certs/www.crt | openssl md5)
KEY_MOD=$(openssl rsa -noout -modulus -in /etc/pki/tls/private/www.key | openssl md5)
[ "$CERT_MOD" = "$KEY_MOD" ] && echo "✅ Match" || echo "❌ No match!"

# 7. Check if port 443 is listening
ss -tlnp | grep :443

# 8. Check firewall
sudo firewall-cmd --list-services | grep https

# 9. Test locally
curl -vk https://localhost/

# 10. Check logs
sudo tail -f /var/log/httpd/ssl_error_log

Common Errors and Solutions

Error MessageCauseSolution
“SSLCertificateFile: file does not exist”Wrong pathFix path in ssl.conf
“Permission denied” on key fileWrong permissionschmod 600 on key
“certificate verify failed”Chain issueInstall intermediate certs
“SSLCertificateKeyFile: file does not exist”Missing keyGenerate or restore key
“Private key does not match certificate”Cert/key mismatchRegenerate CSR with correct key
“SSL Library Error”mod_ssl not loadedInstall mod_ssl package
“ca md too weak” (RHEL 9+)SHA-1 signatureReissue with SHA-256+
“name mismatch”Hostname doesn’t match CN/SANFix certificate SANs

14.9 Version-Specific Troubleshooting

RHEL 7 Specific

#============================================#
# RHEL 7 APACHE ISSUES
#============================================#

# Issue: Modern browsers reject TLS 1.0/1.1
# Solution: Disable old TLS in ssl.conf
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1

# Issue: Weak ciphers flagged by scan
# Solution: Use strong ciphers
SSLCipherSuite ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5
SSLHonorCipherOrder on

# Issue: No SANs in certificate
# Solution: Reissue with SANs (see 13.5)

# Test
openssl s_client -connect localhost:443 -tls1_2

RHEL 8/9/10 Specific

#============================================#
# RHEL 8/9/10 APACHE ISSUES
#============================================#

# Issue: Service fails after crypto-policy change
# Diagnosis:
update-crypto-policies --show
sudo journalctl -xe -u httpd | grep -i tls

# Solution 1: Verify policy is correct
sudo update-crypto-policies --set DEFAULT

# Solution 2: Check if you manually override policy
grep -E "SSLProtocol|SSLCipherSuite" /etc/httpd/conf.d/*.conf
# If found, remove (let crypto-policy handle it)

# Issue: "no shared cipher" error
# Diagnosis: Client too old or policy too strict
# Temporary solution:
sudo update-crypto-policies --set LEGACY
sudo systemctl restart httpd

# Proper solution: Update client or create custom policy module

14.10 Security Best Practices

Hardened Apache SSL Configuration

#============================================#
# HARDENED SSL CONFIG (ALL VERSIONS)
#============================================#

<VirtualHost *:443>
    ServerName secure.example.com

    SSLEngine on
    SSLCertificateFile      /etc/pki/tls/certs/secure.crt
    SSLCertificateKeyFile   /etc/pki/tls/private/secure.key

    # RHEL 7: Manual TLS config
    # SSLProtocol TLSv1.2 TLSv1.3
    # SSLCipherSuite ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256
    # SSLHonorCipherOrder on

    # RHEL 8/9/10: Crypto-policies handle above automatically

    # HSTS (force HTTPS for 1 year)
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

    # Prevent clickjacking
    Header always set X-Frame-Options "DENY"

    # Prevent MIME-type sniffing
    Header always set X-Content-Type-Options "nosniff"

    # Disable server signature
    ServerSignature Off
    ServerTokens Prod

    # OCSP Stapling (RHEL 8/9/10)
    SSLUseStapling on
    SSLStaplingCache "shmcb:/var/run/ocsp(128000)"

    # Client certificate auth (optional)
    # SSLVerifyClient require
    # SSLVerifyDepth 3
    # SSLCACertificateFile /etc/pki/tls/certs/client-ca.crt
</VirtualHost>

# Outside VirtualHost (global SSL settings)
SSLStaplingCache "shmcb:/var/run/ocsp(128000)"

14.11 HTTP to HTTPS Redirect

Force HTTPS

#============================================#
# REDIRECT HTTP → HTTPS
#============================================#

# Method 1: Separate VirtualHost
<VirtualHost *:80>
    ServerName www.example.com
    Redirect permanent / https://www.example.com/
</VirtualHost>

<VirtualHost *:443>
    ServerName www.example.com
    # ... SSL config ...
</VirtualHost>

# Method 2: mod_rewrite
<VirtualHost *:80>
    ServerName www.example.com

    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
</VirtualHost>

14.12 Testing Apache HTTPS

Comprehensive Testing

#============================================#
# APACHE HTTPS TESTING SUITE
#============================================#

# Test 1: Configuration syntax
sudo apachectl configtest

# Test 2: SSL module loaded
sudo httpd -M | grep ssl

# Test 3: Port listening
ss -tlnp | grep :443

# Test 4: Local connection
curl -vk https://localhost/

# Test 5: Actual hostname
curl -v https://www.example.com/

# Test 6: Certificate validation
openssl s_client -connect www.example.com:443 -servername www.example.com

# Test 7: TLS 1.2
openssl s_client -connect www.example.com:443 -tls1_2

# Test 8: TLS 1.3 (RHEL 8+)
openssl s_client -connect www.example.com:443 -tls1_3

# Test 9: Check certificate details from server
echo | openssl s_client -connect www.example.com:443 -servername www.example.com 2>&1 | \
  openssl x509 -noout -text | head -30

# Test 10: Online test (external)
# Use: https://www.ssllabs.com/ssltest/

14.13 Performance Optimization

SSL/TLS Performance Tuning

#============================================#
# PERFORMANCE TUNING
#============================================#

<VirtualHost *:443>
    # ... basic config ...

    # Session cache (improves performance)
    SSLSessionCache         "shmcb:/var/cache/httpd/ssl_scache(512000)"
    SSLSessionCacheTimeout  300

    # OCSP Stapling (reduces client-side lookup)
    SSLUseStapling on
    SSLStaplingCache "shmcb:/var/run/ocsp(128000)"

    # Keep-Alive (reuse connections)
    KeepAlive On
    MaxKeepAliveRequests 100
    KeepAliveTimeout 5

    # HTTP/2 (RHEL 8/9/10)
    Protocols h2 h2c http/1.1
</VirtualHost>

14.14 Monitoring Apache HTTPS

What to Monitor

#============================================#
# APACHE HTTPS MONITORING
#============================================#

# Certificate expiration
openssl s_client -connect localhost:443 -servername $(hostname -f) 2>/dev/null | \
  openssl x509 -noout -dates

# Service status
systemctl status httpd

# Connection count
ss -tn | grep :443 | wc -l

# Error log monitoring
sudo tail -f /var/log/httpd/ssl_error_log

# certmonger status (if used)
sudo getcert list -f /etc/pki/tls/certs/www.crt

# Access log analysis
sudo tail -f /var/log/httpd/ssl_access_log | grep -E "HTTP/[12]"

14.15 Quick Troubleshooting Guide

Apache HTTPS Not Working?

├─ Apache won't start?
│  ├─ Check: apachectl configtest
│  ├─ Check: journalctl -xe -u httpd
│  └─ Fix: Configuration errors
│
├─ Can't connect to port 443?
│  ├─ Check: ss -tlnp | grep :443
│  ├─ Check: firewall-cmd --list-services
│  └─ Fix: Open firewall, start httpd
│
├─ Certificate warnings in browser?
│  ├─ Check: Certificate expiration
│  ├─ Check: Hostname match (CN/SANs)
│  ├─ Check: Trust chain
│  └─ Fix: Renew cert, fix SANs, install CA
│
├─ "No shared cipher" error?
│  ├─ Check: update-crypto-policies --show
│  ├─ Check: Client TLS version
│  └─ Fix: Update policy or client
│
└─ Permission errors?
   ├─ Check: ls -lZ /etc/pki/tls/private/*.key
   ├─ Check: SELinux denials
   └─ Fix: chmod 600, restorecon

14.16 Key Takeaways

  1. Apache + mod_ssl is the standard RHEL web server
  2. RHEL 7: Manual TLS configuration required
  3. RHEL 8/9/10: Crypto-policies simplify configuration
  4. certmonger integration enables automation
  5. certbot requires EPEL (not officially supported)
  6. Always use SANs in certificates
  7. Test thoroughly before production deployment

Quick Reference Card

┌───────────────────────────────────────────────────────────────────┐
│ APACHE HTTPD HTTPS QUICK REFERENCE                                │
├───────────────────────────────────────────────────────────────────┤
│ Install:      dnf install httpd mod_ssl                           │
│ Config:       /etc/httpd/conf.d/ssl.conf                          │
│ Certs:        /etc/pki/tls/certs/                                 │
│ Keys:         /etc/pki/tls/private/ (mode 600!)                   │
│                                                                   │
│ Test config:  apachectl configtest                                │
│ Reload:       systemctl reload httpd                              │
│ Logs:         /var/log/httpd/ssl_error_log                        │
│                                                                   │
│ certmonger:   ipa-getcert request ... -C "systemctl reload httpd" │
│ certbot:      certbot --apache (requires EPEL!)                   │
│                                                                   │
│ Test:         curl -v https://localhost/                          │
│               openssl s_client -connect host:443                  │
└───────────────────────────────────────────────────────────────────┘

⚠️ RHEL 8/9/10: Let crypto-policies handle TLS/cipher config
⚠️ certbot requires EPEL (not officially supported)

🧪 Hands-On Lab

Lab 06: Apache HTTPS Setup

Configure Apache with SSL/TLS across RHEL versions

  • 📁 Location: labs/en_US/06-apache-https/
  • ⏱️ Time: 30-40 minutes
  • 🎯 Level: Intermediate

Chapter Navigation

Chapter 15: NGINX on RHEL

High Performance: NGINX is a popular high-performance web server and reverse proxy. Learn how to configure NGINX with TLS certificates on RHEL.


15.1 NGINX on RHEL Overview

Package Name: nginx Config Location: /etc/nginx/nginx.conf Certificate Path: /etc/pki/tls/certs/ or /etc/nginx/certs/ Key Path: /etc/pki/tls/private/ or /etc/nginx/certs/

Installation Sources by RHEL Version

RHEL VersionNGINX SourceHow to Install
RHEL 7EPEL (community)Enable EPEL, then yum install nginx
RHEL 8AppStream (official)dnf module install nginx:1.20
RHEL 9AppStream (official)dnf install nginx
RHEL 10AppStream (official)dnf install nginx

Note: RHEL 7 requires EPEL for NGINX. RHEL 8+ includes NGINX in official repos.


15.2 Installation

RHEL 7

#============================================#
# INSTALL NGINX (RHEL 7 - REQUIRES EPEL)
#============================================#

# Step 1: Enable EPEL
sudo yum install epel-release -y

# Step 2: Install NGINX
sudo yum install nginx -y

# Step 3: Enable and start
sudo systemctl enable nginx
sudo systemctl start nginx

# Step 4: Open firewall
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

# Verify
systemctl status nginx
curl http://localhost/

RHEL 8

#============================================#
# INSTALL NGINX (RHEL 8 - FROM APPSTREAM)
#============================================#

# List available NGINX modules
dnf module list nginx

# Install specific version
sudo dnf module install nginx:1.20 -y

# Or install default
sudo dnf install nginx -y

# Enable and start
sudo systemctl enable nginx
sudo systemctl start nginx

# Open firewall
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

RHEL 9/10

#============================================#
# INSTALL NGINX (RHEL 9/10)
#============================================#

sudo dnf install nginx -y

sudo systemctl enable nginx
sudo systemctl start nginx

# Open firewall
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

# Verify
systemctl status nginx
ss -tlnp | grep :443

15.3 Basic HTTPS Configuration

Minimal TLS Setup

#============================================#
# /etc/nginx/nginx.conf or /etc/nginx/conf.d/default.conf
#============================================#

server {
    listen 80;
    server_name www.example.com;

    # Redirect HTTP to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name www.example.com;

    # Certificate files
    ssl_certificate     /etc/pki/tls/certs/www.example.com.crt;
    ssl_certificate_key /etc/pki/tls/private/www.example.com.key;

    # TLS protocols
    ssl_protocols TLSv1.2 TLSv1.3;

    # Ciphers
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    # Root directory
    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

Hardened Production Configuration

#============================================#
# PRODUCTION-GRADE NGINX HTTPS CONFIG
#============================================#

server {
    listen 443 ssl http2;
    server_name api.example.com;

    # Certificates
    ssl_certificate     /etc/pki/tls/certs/api.example.com.crt;
    ssl_certificate_key /etc/pki/tls/private/api.example.com.key;

    # TLS versions
    ssl_protocols TLSv1.2 TLSv1.3;

    # Strong ciphers
    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
    ssl_prefer_server_ciphers on;

    # DH parameters (optional, for perfect forward secrecy)
    ssl_dhparam /etc/nginx/dhparam.pem;

    # SSL session optimization
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_session_tickets off;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/pki/tls/certs/chain.crt;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    # HSTS
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    # Security headers
    add_header X-Frame-Options DENY always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-XSS-Protection "1; mode=block" always;

    # Logging
    access_log /var/log/nginx/api_access.log;
    error_log /var/log/nginx/api_error.log;

    location / {
        proxy_pass http://backend_servers;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

15.4 Certificate Setup

Generate Certificates for NGINX

#============================================#
# CERTIFICATE GENERATION FOR NGINX
#============================================#

# Step 1: Create certs directory (optional)
sudo mkdir -p /etc/nginx/certs
sudo chmod 755 /etc/nginx/certs

# Step 2: Generate private key
sudo openssl genpkey -algorithm RSA \
  -out /etc/pki/tls/private/api.example.com.key \
  -pkeyopt rsa_keygen_bits:2048

# Step 3: Set permissions
sudo chmod 600 /etc/pki/tls/private/api.example.com.key
sudo chown root:nginx /etc/pki/tls/private/api.example.com.key

# Step 4: Generate CSR
sudo openssl req -new \
  -key /etc/pki/tls/private/api.example.com.key \
  -out /tmp/api.example.com.csr \
  -subj "/CN=api.example.com" \
  -addext "subjectAltName=DNS:api.example.com,DNS:www.api.example.com"

# Step 5: Submit to CA, receive certificate

# Step 6: Install certificate
sudo cp api.example.com.crt /etc/pki/tls/certs/
sudo chmod 644 /etc/pki/tls/certs/api.example.com.crt

15.5 certmonger Integration

Automated Renewal with certmonger

#============================================#
# CERTMONGER + NGINX
#============================================#

# Install certmonger
# RHEL 8/9/10
sudo dnf install certmonger

# RHEL 7
# sudo yum install certmonger

sudo systemctl enable --now certmonger

# Request certificate from FreeIPA / internal CA
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/nginx.example.com.crt \
  -k /etc/pki/tls/private/nginx.example.com.key \
  -D nginx.example.com \
  -K host/nginx.example.com@REALM \
  -C "systemctl reload nginx"  # Auto-reload on renewal!

# Monitor status
sudo getcert list

# For public Let's Encrypt certificates, use certbot in section 15.6.

15.6 Let’s Encrypt with certbot

⚠️ IMPORTANT: EPEL Required

certbot is NOT available in official RHEL repositories. It requires EPEL, a community-maintained repository.

Installation: All RHEL versions require EPEL, but the enablement command differs by version. See Chapter 24 for the full certbot workflow.

RHEL 7

# Step 1: Enable EPEL
sudo yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -y

# Step 2: Install certbot with NGINX plugin
sudo yum install certbot python2-certbot-nginx -y

RHEL 8

# Step 1: Enable EPEL
sudo dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm -y
# Or, with an active subscription:
# sudo dnf install epel-release -y

# Step 2: Install certbot with NGINX plugin
sudo dnf install certbot python3-certbot-nginx -y

RHEL 9/10

# Step 1: Enable EPEL
sudo dnf install epel-release -y

# Step 2: Install certbot with NGINX plugin
sudo dnf install certbot python3-certbot-nginx -y

Obtain and configure the certificate (all versions)

# Step 3: Obtain and install certificate (automated!)
sudo certbot --nginx -d www.example.com -d example.com

# Certbot will:
#  ✅ Generate certificate from Let's Encrypt
#  ✅ Update NGINX configuration automatically
#  ✅ Set up HTTP to HTTPS redirect
#  ✅ Configure auto-renewal

# Step 4: Verify auto-renewal timer
systemctl list-timers | grep certbot

# Step 5: Test renewal (dry run)
sudo certbot renew --dry-run

# Certificate renews automatically every 60 days!

Remember: EPEL is community-supported, not Red Hat supported. For enterprise production, consider FreeIPA + certmonger.


15.7 Reverse Proxy with TLS

NGINX as TLS Termination Proxy

#============================================#
# NGINX REVERSE PROXY WITH TLS
#============================================#

upstream backend_servers {
    server 10.0.1.10:8080;
    server 10.0.1.11:8080;
    server 10.0.1.12:8080;
}

server {
    listen 443 ssl http2;
    server_name proxy.example.com;

    # TLS termination here
    ssl_certificate     /etc/pki/tls/certs/proxy.crt;
    ssl_certificate_key /etc/pki/tls/private/proxy.key;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # Proxy to backends (HTTP)
    location / {
        proxy_pass http://backend_servers;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }
}

15.8 Troubleshooting NGINX HTTPS

Diagnostic Commands

#============================================#
# NGINX HTTPS TROUBLESHOOTING
#============================================#

# Test configuration syntax
sudo nginx -t

# Show full configuration (with includes)
sudo nginx -T

# Check SSL certificate paths
sudo nginx -T | grep ssl_certificate

# Verify certificate file
sudo openssl x509 -in /etc/pki/tls/certs/nginx.crt -noout -text

# Verify key file
sudo openssl rsa -in /etc/pki/tls/private/nginx.key -check

# Check cert/key pair match
CERT=$(openssl x509 -noout -modulus -in /etc/pki/tls/certs/nginx.crt | openssl md5)
KEY=$(openssl rsa -noout -modulus -in /etc/pki/tls/private/nginx.key | openssl md5)
[ "$CERT" = "$KEY" ] && echo "✅ Match" || echo "❌ Mismatch!"

# Check if NGINX is listening on 443
ss -tlnp | grep :443

# Check SELinux context
ls -Z /etc/pki/tls/certs/nginx.crt
ls -Z /etc/pki/tls/private/nginx.key

# Test HTTPS locally
curl -vk https://localhost/

# Check logs
sudo tail -f /var/log/nginx/error.log

Common NGINX HTTPS Errors

ErrorCauseSolution
“SSL: error:0200100D…”Permission denied on keychmod 600 on key file
“no ssl configured for the server”Missing ssl on listenAdd listen 443 ssl;
“cannot load certificate”File not found or invalidCheck path and cert format
“PEM_read_bio:no start line”Wrong formatEnsure cert is PEM format
“key values mismatch”Cert/key don’t matchRegenerate with correct key
“nginx: [emerg] bind() failed”Port already in useCheck ss -tlnp | grep :443

15.9 Version-Specific Configuration

RHEL 7: Manual TLS Configuration

#============================================#
# NGINX RHEL 7 - MANUAL SSL CONFIG
#============================================#

server {
    listen 443 ssl;
    server_name www.example.com;

    ssl_certificate     /etc/pki/tls/certs/www.crt;
    ssl_certificate_key /etc/pki/tls/private/www.key;

    # REQUIRED: Manually disable weak TLS
    ssl_protocols TLSv1.2;  # No TLS 1.0/1.1!

    # REQUIRED: Manually set strong ciphers
    ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5';
    ssl_prefer_server_ciphers on;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000" always;

    root /usr/share/nginx/html;
}

RHEL 8/9/10: Crypto-Policies Aware

#============================================#
# NGINX RHEL 8/9/10 - WITH CRYPTO-POLICIES
#============================================#

server {
    listen 443 ssl http2;
    server_name www.example.com;

    ssl_certificate     /etc/pki/tls/certs/www.crt;
    ssl_certificate_key /etc/pki/tls/private/www.key;

    # Minimal TLS config - crypto-policies handle the rest!
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    # Or completely rely on crypto-policies:
    # (remove ssl_protocols and ssl_ciphers)
    # NGINX will use system crypto-policy

    # Session optimization
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/pki/tls/certs/chain.crt;

    # HSTS
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    root /usr/share/nginx/html;
}

15.10 Performance Optimization

SSL/TLS Performance Tuning

#============================================#
# NGINX SSL PERFORMANCE OPTIMIZATION
#============================================#

http {
    # SSL session cache (reduces handshake overhead)
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # Buffer sizes
    ssl_buffer_size 4k;  # Smaller = lower latency, larger = better throughput

    server {
        listen 443 ssl http2;
        server_name fast.example.com;

        ssl_certificate     /etc/pki/tls/certs/fast.crt;
        ssl_certificate_key /etc/pki/tls/private/fast.key;

        # Use HTTP/2 for multiplexing
        # (already enabled in listen directive)

        # Enable OCSP Stapling (reduces client lookup time)
        ssl_stapling on;
        ssl_stapling_verify on;

        # Keep-alive
        keepalive_timeout 70;
        keepalive_requests 100;

        location / {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }
    }
}

15.11 Multiple Certificates (SNI)

Server Name Indication (SNI)

#============================================#
# MULTIPLE DOMAINS WITH DIFFERENT CERTIFICATES
#============================================#

# Site 1
server {
    listen 443 ssl http2;
    server_name site1.example.com;

    ssl_certificate     /etc/pki/tls/certs/site1.crt;
    ssl_certificate_key /etc/pki/tls/private/site1.key;

    root /var/www/site1;
}

# Site 2
server {
    listen 443 ssl http2;
    server_name site2.example.com;

    ssl_certificate     /etc/pki/tls/certs/site2.crt;
    ssl_certificate_key /etc/pki/tls/private/site2.key;

    root /var/www/site2;
}

# Site 3 (wildcard)
server {
    listen 443 ssl http2;
    server_name *.apps.example.com;

    ssl_certificate     /etc/pki/tls/certs/wildcard.apps.crt;
    ssl_certificate_key /etc/pki/tls/private/wildcard.apps.key;

    root /var/www/apps;
}

15.12 Client Certificate Authentication

Mutual TLS (mTLS) with NGINX

#============================================#
# NGINX WITH CLIENT CERTIFICATE AUTH
#============================================#

server {
    listen 443 ssl http2;
    server_name secure.example.com;

    # Server certificates
    ssl_certificate     /etc/pki/tls/certs/secure.crt;
    ssl_certificate_key /etc/pki/tls/private/secure.key;

    # Client certificate verification
    ssl_client_certificate /etc/pki/tls/certs/client-ca.crt;
    ssl_verify_client on;  # or 'optional'
    ssl_verify_depth 3;

    # Pass client cert info to backend
    location / {
        proxy_pass http://backend;
        proxy_set_header X-SSL-Client-Cert $ssl_client_cert;
        proxy_set_header X-SSL-Client-DN $ssl_client_s_dn;
        proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
    }
}

15.13 Testing NGINX HTTPS

Comprehensive Test Suite

#============================================#
# NGINX HTTPS TESTING
#============================================#

# Test 1: Configuration syntax
sudo nginx -t
# nginx: configuration file /etc/nginx/nginx.conf test is successful

# Test 2: Show effective configuration
sudo nginx -T | grep -A10 "server_name www.example.com"

# Test 3: Port listening
ss -tlnp | grep nginx

# Test 4: Local HTTPS
curl -vk https://localhost/

# Test 5: Hostname-based
curl -v https://www.example.com/

# Test 6: Check certificate from server
echo | openssl s_client -connect www.example.com:443 -servername www.example.com 2>&1 | \
  openssl x509 -noout -subject -dates

# Test 7: TLS 1.2
openssl s_client -connect www.example.com:443 -tls1_2

# Test 8: TLS 1.3 (RHEL 8+)
openssl s_client -connect www.example.com:443 -tls1_3

# Test 9: HTTP/2 support
curl -I --http2 https://www.example.com/

# Test 10: Security headers
curl -I https://www.example.com/ | grep -i "strict-transport"

15.14 Common Issues and Solutions

Issue 1: “Permission denied” on Private Key

# Symptom
sudo nginx -t
# nginx: [emerg] SSL_CTX_use_PrivateKey_file() failed (SSL: error:0200100D:system library:fopen:Permission denied)

# Check permissions
ls -l /etc/pki/tls/private/nginx.key

# Fix
sudo chmod 600 /etc/pki/tls/private/nginx.key
sudo chown root:nginx /etc/pki/tls/private/nginx.key

# If SELinux issue:
sudo restorecon -v /etc/pki/tls/private/nginx.key

Issue 2: Certificate Chain Not Sent

# Test from client
openssl s_client -connect www.example.com:443 -showcerts

# If shows only server cert (not intermediates):
# Create certificate bundle
cat server.crt intermediate.crt > /etc/pki/tls/certs/bundle.crt

# Update NGINX config
ssl_certificate /etc/pki/tls/certs/bundle.crt;

# Reload
sudo systemctl reload nginx

Issue 3: OCSP Stapling Not Working

# Test OCSP stapling
openssl s_client -connect www.example.com:443 -status -tlsextdebug 2>&1 | grep -A17 "OCSP"

# Common causes:
# 1. No resolver configured
# Fix: Add to nginx.conf:
resolver 8.8.8.8 8.8.4.4 valid=300s;

# 2. Missing trusted certificate chain
# Fix:
ssl_trusted_certificate /etc/pki/tls/certs/chain.crt;

# 3. Firewall blocks OCSP requests
# Fix: Allow outbound HTTPS

15.15 Security Best Practices

Checklist

✅ Use TLS 1.2+ only (disable 1.0/1.1)
✅ Strong ciphers with forward secrecy (ECDHE)
✅ Enable HSTS with long max-age
✅ Enable OCSP Stapling
✅ Use HTTP/2
✅ Proper file permissions (600 for keys)
✅ SELinux contexts correct
✅ Certificate validity ≤ 90 days with auto-renewal
✅ Include comprehensive SANs
✅ Security headers enabled

15.16 Key Takeaways

  1. NGINX available in AppStream (RHEL 8+) or EPEL (RHEL 7)
  2. Crypto-policies simplify config on RHEL 8/9/10
  3. certmonger integrates well with automatic reload
  4. certbot requires EPEL on all RHEL versions
  5. SNI enables multiple certs on same IP
  6. mTLS possible for client authentication
  7. Test thoroughly - syntax, connectivity, security

Quick Reference Card

┌──────────────────────────────────────────────────────────────┐
│ NGINX HTTPS QUICK REFERENCE                                  │
├──────────────────────────────────────────────────────────────┤
│ Install:      dnf install nginx (RHEL 8/9/10)                │
│               yum install epel-release nginx (RHEL 7)        │
│                                                              │
│ Config:       /etc/nginx/nginx.conf                          │
│               /etc/nginx/conf.d/*.conf                       │
│                                                              │
│ Basic SSL:    listen 443 ssl http2;                          │
│               ssl_certificate /path/to/cert.crt;             │
│               ssl_certificate_key /path/to/key.key;          │
│                                                              │
│ Test:         nginx -t                                       │
│ Reload:       systemctl reload nginx                         │
│ Logs:         /var/log/nginx/error.log                       │
│                                                              │
│ certbot:      certbot --nginx (requires EPEL!)               │
│ certmonger:   ipa-getcert ... -C "systemctl reload nginx"    │
└──────────────────────────────────────────────────────────────┘

⚠️ certbot requires EPEL on all RHEL versions
✅ Use certmonger for enterprise environments

🧪 Hands-On Lab

Lab 07: NGINX HTTPS Setup

Configure NGINX with SSL/TLS and security best practices

  • 📁 Location: labs/en_US/07-nginx-https/
  • ⏱️ Time: 30-35 minutes
  • 🎯 Level: Intermediate

Chapter Navigation

Chapter 16: Postfix Mail Server TLS

Email Security: Learn how to configure TLS encryption for Postfix mail servers on RHEL, protecting email communications with certificates.


16.1 Postfix on RHEL Overview

Package Name: postfix Config Location: /etc/postfix/main.cf Certificate Path: /etc/pki/tls/certs/ Key Path: /etc/pki/tls/private/ Ports: 25 (SMTP), 465 (SMTPS), 587 (Submission with STARTTLS)

Why TLS for Email?

  • Encrypt email in transit (prevent eavesdropping)
  • Authenticate mail servers (prevent impersonation)
  • Meet compliance requirements (HIPAA, PCI-DSS)
  • Prevent spam/phishing (SPF, DKIM, DMARC work better with TLS)

16.2 Installation

All RHEL Versions

#============================================#
# INSTALL POSTFIX
#============================================#

# Install Postfix
sudo dnf install postfix -y

# Stop and disable Sendmail (if present)
sudo systemctl stop sendmail 2>/dev/null
sudo systemctl disable sendmail 2>/dev/null

# Enable Postfix
sudo systemctl enable postfix
sudo systemctl start postfix

# Open firewall
sudo firewall-cmd --permanent --add-service=smtp
sudo firewall-cmd --permanent --add-service=smtps
sudo firewall-cmd --permanent --add-service=smtp-submission
sudo firewall-cmd --reload

# Verify
systemctl status postfix
ss -tlnp | grep -E ':(25|465|587)'

16.3 Server-Side TLS Configuration

Receiving Mail with TLS (SMTPD)

#============================================#
# /etc/postfix/main.cf - SERVER (RECEIVING) TLS
#============================================#

# Certificate files
smtpd_tls_cert_file = /etc/pki/tls/certs/mail.example.com.crt
smtpd_tls_key_file = /etc/pki/tls/private/mail.example.com.key
smtpd_tls_CAfile = /etc/pki/tls/certs/ca-bundle.crt

# TLS security level
smtpd_tls_security_level = may  # or 'encrypt' to require TLS

# Logging
smtpd_tls_loglevel = 1
smtpd_tls_received_header = yes

# Session cache (performance)
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache

# Authentication - only over TLS
smtpd_tls_auth_only = yes

# RHEL 7: Specify protocols manually
# smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
# smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1

# RHEL 8/9/10: Crypto-policies handle protocols automatically
# (no need to specify smtpd_tls_protocols)

Security Levels Explained:

LevelBehaviorUse Case
noneTLS disabledNot recommended
mayTLS optional (opportunistic)Standard (compatible)
encryptTLS requiredHigh security environments
daneDNSSEC-based validationAdvanced setups

16.4 Client-Side TLS Configuration

Sending Mail with TLS (SMTP)

#============================================#
# /etc/postfix/main.cf - CLIENT (SENDING) TLS
#============================================#

# Certificate files (optional for client)
smtp_tls_cert_file = /etc/pki/tls/certs/mail.example.com.crt
smtp_tls_key_file = /etc/pki/tls/private/mail.example.com.key
smtp_tls_CAfile = /etc/pki/tls/certs/ca-bundle.crt

# TLS security level for outbound
smtp_tls_security_level = may  # or 'encrypt' to require

# Logging
smtp_tls_loglevel = 1

# Session cache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# RHEL 7: Specify protocols
# smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
# smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1

# RHEL 8/9/10: Crypto-policies handle it

16.5 Complete Postfix TLS Setup

Full Configuration Example

#============================================#
# COMPLETE POSTFIX TLS CONFIGURATION
#============================================#

# 1. Generate certificate and key
sudo openssl genpkey -algorithm RSA \
  -out /etc/pki/tls/private/mail.example.com.key \
  -pkeyopt rsa_keygen_bits:2048

sudo chmod 600 /etc/pki/tls/private/mail.example.com.key

# 2. Generate CSR
sudo openssl req -new \
  -key /etc/pki/tls/private/mail.example.com.key \
  -out /tmp/mail.example.com.csr \
  -subj "/CN=mail.example.com" \
  -addext "subjectAltName=DNS:mail.example.com,DNS:smtp.example.com"

# 3. Get certificate from CA, install it
sudo cp mail.example.com.crt /etc/pki/tls/certs/
sudo chmod 644 /etc/pki/tls/certs/mail.example.com.crt

# 4. Edit /etc/postfix/main.cf
sudo vi /etc/postfix/main.cf

# Add TLS configuration (see sections 16.3 and 16.4)

# 5. Test configuration
sudo postfix check

# 6. Reload Postfix
sudo systemctl reload postfix

# 7. Test SMTP TLS
openssl s_client -starttls smtp -connect mail.example.com:25

16.6 SMTPS (Port 465) Configuration

Enable SMTPS Service

#============================================#
# /etc/postfix/master.cf - ENABLE SMTPS
#============================================#

# Uncomment or add SMTPS service (port 465)
smtps     inet  n       -       n       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

# Reload
sudo systemctl reload postfix

# Verify
ss -tlnp | grep :465

Test SMTPS

# Connect to SMTPS (immediate TLS, no STARTTLS)
openssl s_client -connect mail.example.com:465

# Should show TLS handshake immediately

16.7 Submission Port (587) with STARTTLS

Enable Submission Service

#============================================#
# /etc/postfix/master.cf - ENABLE SUBMISSION
#============================================#

# Uncomment or add submission service (port 587)
submission inet n       -       n       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
  -o milter_macro_daemon_name=ORIGINATING

# Reload
sudo systemctl reload postfix

# Verify
ss -tlnp | grep :587

Test Submission Port

# Test STARTTLS on port 587
openssl s_client -starttls smtp -connect mail.example.com:587

# Should show:
# STARTTLS
# 220 2.0.0 Ready to start TLS

16.8 certmonger Integration

Automated Certificate Management for Postfix

#============================================#
# CERTMONGER + POSTFIX
#============================================#

# Install certmonger
# RHEL 8/9/10
sudo dnf install certmonger

# RHEL 7
# sudo yum install certmonger
sudo systemctl enable --now certmonger

# Request certificate from FreeIPA
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/mail.example.com.crt \
  -k /etc/pki/tls/private/mail.example.com.key \
  -D mail.example.com \
  -K host/mail.example.com@REALM \
  -C "postfix reload"  # Auto-reload Postfix after renewal

# Check status
sudo getcert list

# Postfix automatically uses renewed certificate!

16.9 Client Certificate Authentication

Require Client Certificates (mTLS for SMTP)

#============================================#
# /etc/postfix/main.cf - CLIENT CERT AUTH
#============================================#

# Server certificates (as before)
smtpd_tls_cert_file = /etc/pki/tls/certs/mail.crt
smtpd_tls_key_file = /etc/pki/tls/private/mail.key

# Client certificate verification
smtpd_tls_CAfile = /etc/pki/tls/certs/client-ca.crt
smtpd_tls_ask_ccert = yes
smtpd_tls_req_ccert = yes  # Require client cert

# Map certificate to user
smtpd_recipient_restrictions =
  permit_tls_clientcerts,
  reject

# Reload
sudo postfix reload

Test with client certificate:

openssl s_client -starttls smtp \
  -connect mail.example.com:25 \
  -cert client.crt \
  -key client.key \
  -CAfile ca.crt

16.10 Troubleshooting Postfix TLS

Diagnostic Commands

#============================================#
# POSTFIX TLS TROUBLESHOOTING
#============================================#

# Check Postfix configuration
sudo postconf | grep -i tls

# Test configuration syntax
sudo postfix check

# View specific TLS settings
sudo postconf smtpd_tls_cert_file smtpd_tls_key_file

# Check certificate files exist
ls -l /etc/pki/tls/certs/mail.crt
ls -l /etc/pki/tls/private/mail.key

# Verify cert/key pair match
CERT_MOD=$(openssl x509 -noout -modulus -in /etc/pki/tls/certs/mail.crt | openssl md5)
KEY_MOD=$(openssl rsa -noout -modulus -in /etc/pki/tls/private/mail.key | openssl md5)
[ "$CERT_MOD" = "$KEY_MOD" ] && echo "✅ Match" || echo "❌ Mismatch!"

# Test SMTP STARTTLS
openssl s_client -starttls smtp -connect mail.example.com:25

# Test SMTPS (port 465)
openssl s_client -connect mail.example.com:465

# Check logs
sudo tail -f /var/log/maillog | grep -i tls

# Check Postfix queue
mailq

# Verbose logging (for troubleshooting)
sudo postconf smtpd_tls_loglevel=2
sudo postfix reload
# Check /var/log/maillog for detailed TLS info

Common Postfix TLS Issues

ErrorCauseSolution
“SSL_accept error”Certificate/key issueVerify cert/key pair match
“No shared cipher”Cipher incompatibilityCheck crypto-policy or client
“certificate verify failed”Trust chain brokenInstall intermediate certs
“Permission denied” on keyWrong permissionschmod 600 on key file
“TLS is required but not available”TLS not enabledSet smtpd_tls_security_level = may
“STARTTLS failed”Client TLS issueCheck client TLS support

16.11 Version-Specific Considerations

RHEL 7

#============================================#
# POSTFIX TLS - RHEL 7 SPECIFIC
#============================================#

# /etc/postfix/main.cf

# MUST manually specify TLS protocols
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1

smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1

# MUST manually specify ciphers
smtpd_tls_mandatory_ciphers = high
smtpd_tls_ciphers = high

smtp_tls_mandatory_ciphers = high
smtp_tls_ciphers = high

# Exclude weak ciphers
smtpd_tls_mandatory_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA, KRB5-DES, CBC3-SHA

RHEL 8/9/10

#============================================#
# POSTFIX TLS - RHEL 8/9/10 WITH CRYPTO-POLICIES
#============================================#

# /etc/postfix/main.cf

# Certificate files
smtpd_tls_cert_file = /etc/pki/tls/certs/mail.crt
smtpd_tls_key_file = /etc/pki/tls/private/mail.key
smtpd_tls_CAfile = /etc/pki/tls/certs/ca-bundle.crt

# TLS settings (crypto-policies handle protocols/ciphers!)
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_loglevel = 1

# Client TLS
smtp_tls_cert_file = /etc/pki/tls/certs/mail.crt
smtp_tls_key_file = /etc/pki/tls/private/mail.key
smtp_tls_security_level = may
smtp_tls_loglevel = 1

# That's it! Much simpler than RHEL 7
# crypto-policies automatically configure TLS versions and ciphers

Key Difference: RHEL 8+ relies on crypto-policies, making configuration much simpler!


16.12 Testing Postfix TLS

Comprehensive Test Suite

#============================================#
# POSTFIX TLS TESTING
#============================================#

# Test 1: STARTTLS on port 25
openssl s_client -starttls smtp -connect mail.example.com:25

# Look for:
# - "250-STARTTLS" in server capabilities
# - Successful TLS handshake
# - Certificate details

# Test 2: SMTPS on port 465 (direct TLS)
openssl s_client -connect mail.example.com:465

# Test 3: Submission port 587 with STARTTLS
openssl s_client -starttls smtp -connect mail.example.com:587

# Test 4: Verify certificate from server
echo "QUIT" | openssl s_client -starttls smtp -connect mail.example.com:25 2>&1 | \
  openssl x509 -noout -subject -dates

# Test 5: Send test email
echo "Test email" | mail -s "TLS Test" test@example.com

# Test 6: Check TLS in logs
sudo tail -f /var/log/maillog | grep TLS

# Test 7: Check if TLS is being used
sudo postconf | grep -i tls | grep -i level

16.13 Security Best Practices

Hardened Postfix TLS Configuration

#============================================#
# HARDENED POSTFIX TLS CONFIG
#============================================#

# /etc/postfix/main.cf

# Server TLS (receiving mail)
smtpd_tls_cert_file = /etc/pki/tls/certs/mail.crt
smtpd_tls_key_file = /etc/pki/tls/private/mail.key
smtpd_tls_CAfile = /etc/pki/tls/certs/ca-bundle.crt

# REQUIRE TLS for authentication
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes

# Client TLS (sending mail)
smtp_tls_cert_file = /etc/pki/tls/certs/mail.crt
smtp_tls_key_file = /etc/pki/tls/private/mail.key
smtp_tls_security_level = may

# RHEL 7: Manually disable weak protocols
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1

# Mandatory for authenticated connections
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1

# Cipher settings (RHEL 7)
smtpd_tls_mandatory_ciphers = high
smtpd_tls_exclude_ciphers = aNULL, eNULL, EXPORT, DES, RC4, MD5, PSK, aECDH, EDH-DSS-DES-CBC3-SHA, EDH-RSA-DES-CBC3-SHA, KRB5-DES, CBC3-SHA

# Session caching
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# Enhanced logging (temporary for troubleshooting)
smtpd_tls_loglevel = 1
smtp_tls_loglevel = 1

# Performance
smtpd_tls_session_cache_timeout = 3600s
tls_random_source = dev:/dev/urandom

16.14 Dovecot IMAP/POP3 with TLS

Dovecot Configuration

While this chapter focuses on Postfix (SMTP), Dovecot often runs alongside for IMAP/POP3:

#============================================#
# DOVECOT TLS (/etc/dovecot/conf.d/10-ssl.conf)
#============================================#

# Enable SSL
ssl = required

# Certificate files
ssl_cert = </etc/pki/tls/certs/mail.example.com.crt
ssl_key = </etc/pki/tls/private/mail.example.com.key

# CA for client cert verification (optional)
#ssl_ca = </etc/pki/tls/certs/ca-bundle.crt

# RHEL 8/9/10: crypto-policies handle it

# TLS protocols (RHEL 7)
#ssl_min_protocol = TLSv1.2

# Cipher suite preferences
#ssl_cipher_list = HIGH:!aNULL:!MD5

# Reload
sudo systemctl reload dovecot

Test IMAPS:

openssl s_client -connect mail.example.com:993

# Test POP3S
openssl s_client -connect mail.example.com:995

16.15 Common Scenarios

Scenario 1: Opportunistic TLS (Standard)

Goal: Accept both encrypted and unencrypted connections

# /etc/postfix/main.cf
smtpd_tls_security_level = may  # Optional TLS
smtp_tls_security_level = may   # Optional TLS

# Why: Maximum compatibility
# Drawback: Some connections may not be encrypted

Scenario 2: Mandatory TLS (High Security)

Goal: Reject unencrypted connections

# /etc/postfix/main.cf
smtpd_tls_security_level = encrypt  # REQUIRE TLS
smtp_tls_security_level = encrypt   # REQUIRE TLS

# Why: Maximum security
# Drawback: Old systems may fail to connect

Scenario 3: Client Certificate for Relay

Goal: Allow relay only with valid client certificate

# /etc/postfix/main.cf
smtpd_tls_cert_file = /etc/pki/tls/certs/mail.crt
smtpd_tls_key_file = /etc/pki/tls/private/mail.key
smtpd_tls_CAfile = /etc/pki/tls/certs/client-ca.crt

smtpd_tls_ask_ccert = yes
smtpd_tls_req_ccert = yes

smtpd_recipient_restrictions =
  permit_tls_clientcerts,
  reject_unauth_destination

# Only clients with valid cert from client-ca.crt can relay

16.16 Monitoring Postfix TLS

What to Monitor

#============================================#
# POSTFIX TLS MONITORING
#============================================#

# Check TLS usage in logs
sudo grep "TLS connection established" /var/log/maillog | tail -20

# Count TLS vs non-TLS connections
sudo grep "connect from" /var/log/maillog | grep -c "TLS"

# Check for TLS errors
sudo grep "TLS" /var/log/maillog | grep -i error

# Monitor certificate expiration
openssl x509 -in /etc/pki/tls/certs/mail.crt -noout -checkend $((86400*30))

# certmonger status (if used)
sudo getcert list -f /etc/pki/tls/certs/mail.crt

# Check queue for TLS failures
mailq

Monitoring Script

#!/bin/bash
# monitor-postfix-tls.sh

echo "=== Postfix TLS Monitoring ==="

# Certificate expiration
echo "1. Certificate Expiration:"
openssl x509 -in /etc/pki/tls/certs/mail.crt -noout -enddate

# TLS connections today
echo ""
echo "2. TLS Connections Today:"
sudo grep "$(date '+%b %e')" /var/log/maillog | grep "TLS connection" | wc -l

# TLS errors today
echo ""
echo "3. TLS Errors Today:"
sudo grep "$(date '+%b %e')" /var/log/maillog | grep -i "tls.*error" | tail -5

# Service status
echo ""
echo "4. Postfix Status:"
systemctl is-active postfix

# Listening ports
echo ""
echo "5. Listening on:"
ss -tlnp | grep master | grep -E ':(25|465|587)'

16.17 Common Issues and Solutions

Issue 1: Certificate Not Trusted by Clients

Symptom: Clients get “untrusted certificate” warnings

Diagnosis:

# Test certificate chain
openssl s_client -starttls smtp -connect mail.example.com:25 -showcerts

# Check if intermediate certificates included
sudo postconf smtpd_tls_cert_file
# Should include full chain or use smtpd_tls_chain_files

Solution:

# Option 1: Include chain in certificate file
cat server.crt intermediate.crt > /etc/pki/tls/certs/mail-chain.crt

# Update config
sudo postconf -e 'smtpd_tls_cert_file = /etc/pki/tls/certs/mail-chain.crt'
sudo postfix reload

# Option 2: Use separate chain file (Postfix 3.4+)
sudo postconf -e 'smtpd_tls_chain_files = /etc/pki/tls/certs/chain.crt'

Issue 2: “Permission denied” on Private Key

Symptom: Postfix won’t start, logs show permission error

Fix:

# Set correct permissions
sudo chmod 600 /etc/pki/tls/private/mail.key
sudo chown root:postfix /etc/pki/tls/private/mail.key

# Fix SELinux context
sudo restorecon -v /etc/pki/tls/private/mail.key

# Restart Postfix
sudo systemctl restart postfix

Issue 3: TLS Not Offered to Clients

Symptom: STARTTLS not shown in EHLO response

Diagnosis:

# Telnet to server
telnet mail.example.com 25
# Type: EHLO test
# Should show: 250-STARTTLS

# If not shown, check config
sudo postconf smtpd_tls_security_level
# Should be 'may' or 'encrypt', not 'none'

Solution:

sudo postconf -e 'smtpd_tls_security_level = may'
sudo postfix reload

16.18 Version Comparison Table

FeatureRHEL 7RHEL 8RHEL 9RHEL 10
Postfix Version2.10.x3.3.x+3.5.x+3.8.x+
OpenSSL1.0.2k1.1.1k3.5.53.5.5
TLS ConfigManualCrypto-policiesCrypto-policiesCrypto-policies
Default TLSMay include 1.0/1.1TLS 1.2+TLS 1.2+TLS 1.3 preferred
certmongerBasicEnhancedEnhancedEnhanced

16.19 Quick Setup Script

#!/bin/bash
# setup-postfix-tls.sh - Complete Postfix TLS setup

DOMAIN="example.com"
HOSTNAME="mail.$DOMAIN"

echo "=== Postfix TLS Setup for $HOSTNAME ==="

# 1. Install Postfix
sudo dnf install -y postfix

# 2. Generate certificate (or use certmonger)
sudo openssl genpkey -algorithm RSA \
  -out /etc/pki/tls/private/$HOSTNAME.key \
  -pkeyopt rsa_keygen_bits:2048

sudo chmod 600 /etc/pki/tls/private/$HOSTNAME.key

# 3. Generate self-signed for testing (replace with proper cert!)
sudo openssl req -new -x509 -days 365 \
  -key /etc/pki/tls/private/$HOSTNAME.key \
  -out /etc/pki/tls/certs/$HOSTNAME.crt \
  -subj "/CN=$HOSTNAME" \
  -addext "subjectAltName=DNS:$HOSTNAME,DNS:smtp.$DOMAIN"

# 4. Configure Postfix
sudo postconf -e "smtpd_tls_cert_file = /etc/pki/tls/certs/$HOSTNAME.crt"
sudo postconf -e "smtpd_tls_key_file = /etc/pki/tls/private/$HOSTNAME.key"
sudo postconf -e "smtpd_tls_security_level = may"
sudo postconf -e "smtpd_tls_auth_only = yes"
sudo postconf -e "smtpd_tls_loglevel = 1"

sudo postconf -e "smtp_tls_cert_file = /etc/pki/tls/certs/$HOSTNAME.crt"
sudo postconf -e "smtp_tls_key_file = /etc/pki/tls/private/$HOSTNAME.key"
sudo postconf -e "smtp_tls_security_level = may"

# 5. Open firewall
sudo firewall-cmd --permanent --add-service=smtp
sudo firewall-cmd --permanent --add-service=smtps
sudo firewall-cmd --permanent --add-service=smtp-submission
sudo firewall-cmd --reload

# 6. Start Postfix
sudo systemctl enable postfix
sudo systemctl restart postfix

# 7. Test
echo "Testing SMTP STARTTLS..."
echo "QUIT" | openssl s_client -starttls smtp -connect localhost:25 2>&1 | grep -E "(CONNECTED|subject=)"

echo "✅ Postfix TLS setup complete!"
echo "⚠️ Replace self-signed cert with proper certificate from CA"

16.20 Key Takeaways

  1. Postfix supports TLS on ports 25 (STARTTLS), 465 (SMTPS), 587 (Submission)
  2. RHEL 7 requires manual TLS config (protocols, ciphers)
  3. RHEL 8+ uses crypto-policies (much simpler!)
  4. Security levels: none, may, encrypt, dane
  5. certmonger works great with Postfix
  6. Always test with openssl s_client -starttls smtp
  7. Monitor logs for TLS errors

Quick Reference Card

┌──────────────────────────────────────────────────────────────┐
│ POSTFIX TLS QUICK REFERENCE                                  │
├──────────────────────────────────────────────────────────────┤
│ Config:       /etc/postfix/main.cf                           │
│ Certs:        smtpd_tls_cert_file = /path/to/cert.crt        │
│ Keys:         smtpd_tls_key_file = /path/to/key.key          │
│ Security:     smtpd_tls_security_level = may|encrypt         │
│                                                              │
│ Test:         openssl s_client -starttls smtp -connect :25   │
│ Reload:       postfix reload                                 │
│ Check:        postfix check                                  │
│ Logs:         tail -f /var/log/maillog | grep TLS            │
│                                                              │
│ Ports:        25 (SMTP+STARTTLS)                             │
│               465 (SMTPS - direct TLS)                       │
│               587 (Submission with STARTTLS)                 │
│                                                              │
│ certmonger:   ipa-getcert ... -C "postfix reload"            │
└──────────────────────────────────────────────────────────────┘

⚠️ RHEL 7: Manual protocol/cipher config needed
✅ RHEL 8/9/10: Crypto-policies handle TLS settings

🧪 Hands-On Lab

Lab 08: Postfix TLS

Configure TLS for Postfix mail server

  • 📁 Location: labs/en_US/08-postfix-tls/
  • ⏱️ Time: 30 minutes
  • 🎯 Level: Intermediate

Chapter Navigation

Chapter 17: OpenLDAP & Directory Services

Enterprise Directory: Learn how to secure OpenLDAP directory services with TLS/SSL on RHEL, protecting user authentication and directory queries.


17.1 LDAP vs LDAPS vs STARTTLS

Three Ways to Secure LDAP

MethodPortEncryptionUse Case
LDAP389❌ NoneLegacy, not recommended
LDAPS636✅ TLS from startPreferred, encrypted
LDAP+STARTTLS389✅ Upgrade to TLSAlternative to LDAPS

Recommendation: Use LDAPS (port 636) for simplicity and security.


17.2 OpenLDAP Installation

All RHEL Versions

#============================================#
# INSTALL OPENLDAP SERVER
#============================================#

# Install OpenLDAP server
sudo dnf install openldap-servers openldap-clients -y

# Start and enable
sudo systemctl enable slapd
sudo systemctl start slapd

# Open firewall
sudo firewall-cmd --permanent --add-service=ldap
sudo firewall-cmd --permanent --add-service=ldaps
sudo firewall-cmd --reload

# Verify
systemctl status slapd
ss -tlnp | grep -E ':(389|636)'

17.3 Generating Certificates for LDAP

Certificate Requirements

LDAP certificates should include:

  • CN matching LDAP server hostname
  • SANs with LDAP server FQDN
  • Server Authentication key usage
  • Valid trust chain
#============================================#
# GENERATE LDAP SERVER CERTIFICATE
#============================================#

# Step 1: Generate private key
sudo openssl genpkey -algorithm RSA \
  -out /etc/openldap/certs/ldap.key \
  -pkeyopt rsa_keygen_bits:2048

# Step 2: Set permissions (important!)
sudo chmod 600 /etc/openldap/certs/ldap.key
sudo chown ldap:ldap /etc/openldap/certs/ldap.key

# Step 3: Generate CSR
sudo openssl req -new \
  -key /etc/openldap/certs/ldap.key \
  -out /tmp/ldap.csr \
  -subj "/CN=ldap.example.com" \
  -addext "subjectAltName=DNS:ldap.example.com,DNS:dir.example.com"

# Step 4: Get certificate from CA

# Step 5: Install certificate
sudo cp ldap.crt /etc/openldap/certs/
sudo chmod 644 /etc/openldap/certs/ldap.crt
sudo chown ldap:ldap /etc/openldap/certs/ldap.crt

# Step 6: Install CA certificate
sudo cp ca.crt /etc/openldap/certs/
sudo chmod 644 /etc/openldap/certs/ca.crt

17.4 OpenLDAP TLS Configuration

Method 1: cn=config (Dynamic Configuration)

#============================================#
# CONFIGURE TLS WITH cn=config (PREFERRED)
#============================================#

# Create LDIF file
cat > /tmp/tls-config.ldif << EOF
dn: cn=config
changetype: modify
add: olcTLSCertificateFile
olcTLSCertificateFile: /etc/openldap/certs/ldap.crt
-
add: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/openldap/certs/ldap.key
-
add: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/openldap/certs/ca.crt
-
replace: olcTLSProtocolMin
olcTLSProtocolMin: 3.2
EOF

# Apply configuration
sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f /tmp/tls-config.ldif

# Restart slapd
sudo systemctl restart slapd

# Verify
sudo slapcat -b "cn=config" | grep -i tls

Method 2: slapd.conf (Legacy)

#============================================#
# CONFIGURE TLS WITH slapd.conf (LEGACY)
#============================================#

# Edit /etc/openldap/slapd.conf

TLSCertificateFile      /etc/openldap/certs/ldap.crt
TLSCertificateKeyFile   /etc/openldap/certs/ldap.key
TLSCACertificateFile    /etc/openldap/certs/ca.crt

# RHEL 7: Manually specify TLS version
TLSProtocolMin          1.2

# Restart
sudo systemctl restart slapd

17.5 Client Configuration

Configure LDAP Client for TLS

#============================================#
# /etc/openldap/ldap.conf - CLIENT CONFIG
#============================================#

# Server URI (use ldaps:// for port 636)
URI ldaps://ldap.example.com

# CA certificate for validation
TLS_CACERT /etc/pki/tls/certs/ca-bundle.crt

# Certificate verification
TLS_REQCERT demand  # require valid certificate

# RHEL 7: Specify minimum TLS version
# TLS_PROTOCOL_MIN 1.2

Test LDAP Client Connection

#============================================#
# TEST LDAPS CLIENT CONNECTION
#============================================#

# Test LDAPS (port 636)
ldapsearch -H ldaps://ldap.example.com:636 \
  -D "cn=admin,dc=example,dc=com" \
  -W \
  -b "dc=example,dc=com" \
  "(objectClass=*)"

# Test LDAP with STARTTLS (port 389)
ldapsearch -H ldap://ldap.example.com:389 -ZZ \
  -D "cn=admin,dc=example,dc=com" \
  -W \
  -b "dc=example,dc=com"

# -ZZ enforces STARTTLS (fails if unavailable)
# -Z attempts STARTTLS (continues without if unavailable)

17.6 FreeIPA Integration

Note: FreeIPA is covered in detail in Chapter 19. This is a quick overview.

FreeIPA Automatically Handles LDAPS

#============================================#
# FREEIPA LDAPS (AUTOMATIC!)
#============================================#

# FreeIPA automatically configures LDAPS
# No manual certificate config needed!

# Test FreeIPA LDAPS
ldapsearch -H ldaps://ipa.example.com:636 \
  -D "uid=admin,cn=users,cn=accounts,dc=example,dc=com" \
  -W \
  -b "dc=example,dc=com"

# FreeIPA certificates managed by certmonger automatically
sudo getcert list | grep -A10 "Directory Server"

17.7 Testing OpenLDAP TLS

Comprehensive Testing

#============================================#
# OPENLDAP TLS TESTING
#============================================#

# Test 1: Check if slapd is listening
ss -tlnp | grep slapd
# Should show ports 389 and/or 636

# Test 2: Test LDAPS connection with OpenSSL
openssl s_client -connect ldap.example.com:636

# Look for:
# - Successful TLS handshake
# - Certificate details
# - Verify return code: 0 (ok)

# Test 3: Test with ldapsearch (anonymous)
ldapsearch -H ldaps://ldap.example.com:636 \
  -x -b "" -s base "(objectClass=*)" namingContexts

# Test 4: Test authenticated query
ldapsearch -H ldaps://ldap.example.com:636 \
  -D "cn=admin,dc=example,dc=com" \
  -W \
  -b "dc=example,dc=com" \
  "(uid=*)"

# Test 5: Test STARTTLS
ldapsearch -H ldap://ldap.example.com:389 -ZZ \
  -x -b "" -s base

# Test 6: Verify certificate from server
echo | openssl s_client -connect ldap.example.com:636 2>&1 | \
  openssl x509 -noout -subject -issuer -dates

17.8 Troubleshooting OpenLDAP TLS

Diagnostic Commands

#============================================#
# OPENLDAP TLS DIAGNOSTICS
#============================================#

# Check slapd configuration
sudo slapcat -b "cn=config" | grep -i tls

# Check certificate files
sudo ls -lZ /etc/openldap/certs/

# Verify permissions
# Key should be readable by 'ldap' user
sudo -u ldap cat /etc/openldap/certs/ldap.key >/dev/null && \
  echo "✅ Key readable" || echo "❌ Permission denied"

# Check SELinux context
ls -Z /etc/openldap/certs/*.{crt,key}

# Check slapd logs
sudo journalctl -u slapd -f

# Test with verbose OpenSSL
openssl s_client -connect ldap.example.com:636 -showcerts -debug

# Test STARTTLS
ldapsearch -H ldap://ldap.example.com:389 -ZZ -d 1

Common OpenLDAP TLS Issues

ErrorCauseSolution
“TLS: can’t connect”Certificate/key not readableCheck ownership: chown ldap:ldap
“TLS: hostname does not match”CN/SAN mismatchRegenerate cert with correct hostname
“Certificate verification failed”CA not trustedAdd CA to client’s trust store
“Permission denied” on keyWrong ownership/permissionschmod 600, chown ldap:ldap
“TLS engine not initialized”TLS not configuredAdd TLS directives to config
“error:14094410:SSL routines”Protocol/cipher mismatchCheck crypto-policy (RHEL 8+)

17.9 Client Certificate Authentication

Require Client Certificates

#============================================#
# OPENLDAP WITH CLIENT CERT AUTH
#============================================#

# Server configuration (cn=config)
cat > /tmp/client-cert.ldif << EOF
dn: cn=config
changetype: modify
add: olcTLSVerifyClient
olcTLSVerifyClient: demand
-
add: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/openldap/certs/client-ca.crt
EOF

sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f /tmp/client-cert.ldif

# Restart
sudo systemctl restart slapd

Client connection with certificate:

# Client must provide certificate
ldapsearch -H ldaps://ldap.example.com:636 \
  -x -b "dc=example,dc=com" \
  -ZZ

# Configure client cert in /etc/openldap/ldap.conf:
TLS_CERT /etc/openldap/certs/client.crt
TLS_KEY /etc/openldap/certs/client.key

17.10 Version-Specific Considerations

RHEL 7

#============================================#
# OPENLDAP TLS - RHEL 7
#============================================#

# Manual TLS protocol specification
# In slapd.conf or cn=config:
TLSProtocolMin 1.2

# Or with cn=config:
olcTLSProtocolMin: 3.2  # 3.1=TLS1.0, 3.2=TLS1.1, 3.3=TLS1.2

# Manual cipher configuration
TLSCipherSuite HIGH:!aNULL:!MD5:!3DES

# Test
openssl s_client -connect ldap.example.com:636 -tls1_2

RHEL 8/9/10

#============================================#
# OPENLDAP TLS - RHEL 8/9/10
#============================================#

# Crypto-policies automatically configure TLS
# No need to specify TLSProtocolMin or ciphers!

# Just configure certificates:
olcTLSCertificateFile: /etc/openldap/certs/ldap.crt
olcTLSCertificateKeyFile: /etc/openldap/certs/ldap.key
olcTLSCACertificateFile: /etc/openldap/certs/ca.crt

# Crypto-policy handles the rest
update-crypto-policies --show

17.11 certmonger with OpenLDAP

Automated Certificate Management

#============================================#
# CERTMONGER + OPENLDAP
#============================================#

# Install certmonger
# RHEL 8/9/10
sudo dnf install certmonger

# RHEL 7
# sudo yum install certmonger
sudo systemctl enable --now certmonger

# Request certificate from FreeIPA
sudo ipa-getcert request \
  -f /etc/openldap/certs/ldap.crt \
  -k /etc/openldap/certs/ldap.key \
  -D ldap.example.com \
  -K ldap/ldap.example.com@REALM \
  -C "systemctl restart slapd"  # Restart slapd after renewal

# Set proper ownership
sudo chown ldap:ldap /etc/openldap/certs/ldap.{crt,key}
sudo chmod 600 /etc/openldap/certs/ldap.key

# Monitor
sudo getcert list

17.12 Troubleshooting LDAPS

Diagnostic Steps

#============================================#
# LDAPS TROUBLESHOOTING
#============================================#

# Step 1: Verify slapd is listening on 636
ss -tlnp | grep 636

# Step 2: Check certificate configuration
sudo slapcat -b "cn=config" | grep olcTLS

# Step 3: Test certificate file
sudo openssl x509 -in /etc/openldap/certs/ldap.crt -noout -text

# Step 4: Test key file
sudo openssl rsa -in /etc/openldap/certs/ldap.key -check

# Step 5: Verify cert/key match
CERT_MOD=$(openssl x509 -noout -modulus -in /etc/openldap/certs/ldap.crt | openssl md5)
KEY_MOD=$(openssl rsa -noout -modulus -in /etc/openldap/certs/ldap.key | openssl md5)
[ "$CERT_MOD" = "$KEY_MOD" ] && echo "✅ Match" || echo "❌ Mismatch!"

# Step 6: Check permissions
ls -l /etc/openldap/certs/
# Key should be owned by ldap:ldap with mode 600

# Step 7: Test connection
openssl s_client -connect ldap.example.com:636

# Step 8: Check logs
sudo journalctl -u slapd | grep -i tls

# Step 9: Test from client
ldapsearch -H ldaps://ldap.example.com:636 -x -b "" -s base

# Step 10: Check SELinux
sudo ausearch -m avc -ts recent | grep ldap

17.13 Common Issues and Solutions

Issue 1: “TLS: can’t accept” Error

Symptom: LDAPS connection refused

Diagnosis:

sudo journalctl -u slapd | grep "TLS: can't accept"

Causes & Solutions:

# Cause 1: Key not readable by ldap user
sudo chown ldap:ldap /etc/openldap/certs/ldap.key
sudo chmod 600 /etc/openldap/certs/ldap.key

# Cause 2: SELinux blocking
sudo restorecon -Rv /etc/openldap/certs/
# Or check for denials:
sudo ausearch -m avc -ts recent | grep ldap

# Cause 3: Certificate/key mismatch
# Regenerate CSR with correct key

Issue 2: “TLS: hostname does not match”

Symptom: Client gets hostname mismatch error

Diagnosis:

# Check certificate CN and SANs
openssl x509 -in /etc/openldap/certs/ldap.crt -noout -subject -ext subjectAltName

Solution:

# Reissue certificate with correct hostname in SANs
openssl req -new -key /etc/openldap/certs/ldap.key -out /tmp/ldap.csr \
  -subj "/CN=ldap.example.com" \
  -addext "subjectAltName=DNS:ldap.example.com,DNS:ldap,IP:10.0.0.10"

# Or on client side: allow looser verification (NOT recommended for production)
# In /etc/openldap/ldap.conf:
TLS_REQCERT allow  # instead of 'demand'

Issue 3: Certificate Not Trusted

Symptom: “Certificate verification failed”

Solution:

# Add CA to system trust store
sudo cp ldap-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

# Or specify CA in client config
# /etc/openldap/ldap.conf:
TLS_CACERT /etc/openldap/certs/ca.crt

17.14 Security Best Practices

Hardened OpenLDAP TLS Configuration

#============================================#
# HARDENED LDAP TLS CONFIG (cn=config)
#============================================#

dn: cn=config
changetype: modify
replace: olcTLSCertificateFile
olcTLSCertificateFile: /etc/openldap/certs/ldap.crt
-
replace: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/openldap/certs/ldap.key
-
replace: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/openldap/certs/ca.crt
-
replace: olcTLSProtocolMin
olcTLSProtocolMin: 3.3
-
replace: olcTLSCipherSuite
olcTLSCipherSuite: HIGH:!aNULL:!MD5:!RC4
-
add: olcTLSVerifyClient
olcTLSVerifyClient: never

Apply:

sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f hardened-tls.ldif
sudo systemctl restart slapd

17.15 Integration with System Services

SSSD with LDAPS (System Authentication)

#============================================#
# CONFIGURE SSSD TO USE LDAPS
#============================================#

# /etc/sssd/sssd.conf

[domain/example.com]
id_provider = ldap
auth_provider = ldap
ldap_uri = ldaps://ldap.example.com:636
ldap_search_base = dc=example,dc=com
ldap_tls_cacert = /etc/pki/tls/certs/ca-bundle.crt
ldap_tls_reqcert = demand

# Restart SSSD
sudo systemctl restart sssd

# Test
id ldapuser@example.com

17.16 Monitoring LDAP TLS

Monitoring Commands

#============================================#
# MONITOR LDAP TLS
#============================================#

# Check certificate expiration
openssl s_client -connect ldap.example.com:636 2>/dev/null | \
  openssl x509 -noout -dates

# Check TLS connections
sudo journalctl -u slapd | grep "TLS established"

# Count LDAPS vs LDAP connections
sudo journalctl -u slapd --since today | grep -c "conn=.*LDAPS"
sudo journalctl -u slapd --since today | grep -c "conn=.*LDAP"

# Check for TLS errors
sudo journalctl -u slapd | grep -i "tls.*error"

# certmonger status (if used)
sudo getcert list -d /etc/openldap/certs

17.17 Key Takeaways

  1. LDAPS (port 636) is simpler than STARTTLS
  2. Certificate ownership critical - Must be readable by ldap user
  3. cn=config preferred over slapd.conf (modern RHEL)
  4. FreeIPA handles LDAPS automatically - Much easier!
  5. Client configuration matters - Set TLS_CACERT correctly
  6. Test thoroughly - Use openssl s_client and ldapsearch -ZZ
  7. certmonger automates certificate renewal

Quick Reference Card

┌──────────────────────────────────────────────────────────────┐
│ OPENLDAP TLS QUICK REFERENCE                                 │
├──────────────────────────────────────────────────────────────┤
│ Config:       cn=config (dynamic) or slapd.conf (legacy)     │
│ Certs:        /etc/openldap/certs/                           │
│ Ownership:    chown ldap:ldap *.{crt,key}                    │
│ Permissions:  chmod 600 ldap.key                             │
│                                                              │
│ LDAPS:        Port 636 (TLS from start)                      │
│ STARTTLS:     Port 389 (upgrade to TLS)                      │
│                                                              │
│ Test LDAPS:   openssl s_client -connect host:636             │
│ Test STLS:    ldapsearch -H ldap://host:389 -ZZ              │
│                                                              │
│ Client:       /etc/openldap/ldap.conf                        │
│               TLS_CACERT /path/to/ca.crt                     │
│               TLS_REQCERT demand                             │
│                                                              │
│ Logs:         journalctl -u slapd | grep TLS                 │
└──────────────────────────────────────────────────────────────┘

⚠️ Key must be owned by 'ldap' user!
✅ Use FreeIPA for easier LDAP+TLS management

🧪 Hands-On Lab

Lab 09: OpenLDAP LDAPS

Configure LDAPS for secure directory services

  • 📁 Location: labs/en_US/09-openldap-ldaps/
  • ⏱️ Time: 35-40 minutes
  • 🎯 Level: Intermediate

Chapter Navigation

Chapter 18: Database TLS (PostgreSQL, MySQL)

Data in Transit: Protect database connections with TLS encryption. Learn how to configure PostgreSQL and MySQL/MariaDB with certificates on RHEL.


18.1 Why Database TLS?

Protect Sensitive Data:

  • ✅ Encrypt database queries and results
  • ✅ Prevent eavesdropping on credentials
  • ✅ Authenticate database servers
  • ✅ Enable client certificate authentication
  • ✅ Meet compliance requirements (PCI-DSS, HIPAA)

Threat Model:

  • Without TLS: Passwords and data travel in cleartext
  • With TLS: All communication encrypted

18.2 PostgreSQL with SSL/TLS

Installation

#============================================#
# INSTALL POSTGRESQL
#============================================#

# RHEL 7/8/9/10
sudo dnf install postgresql-server -y

# Initialize database
sudo postgresql-setup --initdb

# Enable and start
sudo systemctl enable postgresql
sudo systemctl start postgresql

# Verify
systemctl status postgresql
ss -tlnp | grep 5432

Generate PostgreSQL Certificates

#============================================#
# GENERATE POSTGRESQL CERTIFICATES
#============================================#

# Step 1: Generate server key
sudo -u postgres openssl genpkey -algorithm RSA \
  -out /var/lib/pgsql/data/server.key \
  -pkeyopt rsa_keygen_bits:2048

# Step 2: Set permissions (critical!)
sudo chmod 600 /var/lib/pgsql/data/server.key
sudo chown postgres:postgres /var/lib/pgsql/data/server.key

# Step 3: Generate CSR
sudo -u postgres openssl req -new \
  -key /var/lib/pgsql/data/server.key \
  -out /tmp/postgres.csr \
  -subj "/CN=db.example.com" \
  -addext "subjectAltName=DNS:db.example.com,DNS:postgres.example.com"

# Step 4: Get certificate from CA

# Step 5: Install certificate
sudo cp postgres.crt /var/lib/pgsql/data/server.crt
sudo chmod 600 /var/lib/pgsql/data/server.crt
sudo chown postgres:postgres /var/lib/pgsql/data/server.crt

# Step 6: Install CA certificate
sudo cp ca.crt /var/lib/pgsql/data/root.crt
sudo chmod 644 /var/lib/pgsql/data/root.crt

Configure PostgreSQL for SSL

#============================================#
# CONFIGURE POSTGRESQL SSL
#============================================#

# Edit /var/lib/pgsql/data/postgresql.conf
sudo -u postgres vi /var/lib/pgsql/data/postgresql.conf

# Enable SSL
ssl = on

# Certificate files (relative to data directory)
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'
ssl_ca_file = 'root.crt'

# RHEL 7: Specify minimum TLS version
# ssl_min_protocol_version = 'TLSv1.2'

# RHEL 8/9/10: Uses system crypto-policy
# (no need to specify ssl_min_protocol_version)

# Optional: Prefer server ciphers
ssl_prefer_server_ciphers = on

# Restart PostgreSQL
sudo systemctl restart postgresql

Configure Client Authentication

#============================================#
# /var/lib/pgsql/data/pg_hba.conf
#============================================#

# Require SSL for all connections
hostssl all all 0.0.0.0/0 md5

# Require client certificate
hostssl all alice 0.0.0.0/0 cert clientcert=verify-full

# Reload configuration
sudo systemctl reload postgresql

HBA Types:

  • host: Allow non-SSL
  • hostssl: Require SSL
  • hostnossl: Explicitly forbid SSL

Client Cert Options:

  • md5: SSL required, password auth
  • cert: SSL + client certificate required
  • clientcert=verify-full: Verify client cert against CA

Test PostgreSQL SSL

#============================================#
# TEST POSTGRESQL SSL
#============================================#

# Test 1: Connect with SSL required
psql "host=db.example.com port=5432 user=testuser dbname=testdb sslmode=require"

# Test 2: Connect with full verification
psql "host=db.example.com port=5432 user=testuser dbname=testdb sslmode=verify-full sslrootcert=/etc/pki/tls/certs/ca-bundle.crt"

# Test 3: With client certificate
psql "host=db.example.com port=5432 user=alice dbname=mydb sslmode=verify-full sslcert=/home/alice/.postgresql/client.crt sslkey=/home/alice/.postgresql/client.key sslrootcert=/etc/pki/tls/certs/ca-bundle.crt"

# Test 4: Check SSL from within PostgreSQL
psql -h db.example.com -U testuser -d testdb -c "SELECT ssl, version FROM pg_stat_ssl WHERE pid = pg_backend_pid();"

# Test 5: OpenSSL test
openssl s_client -connect db.example.com:5432 -starttls postgres

SSL Modes:

  • disable: No SSL
  • allow: Try SSL, fallback to non-SSL
  • prefer: Prefer SSL, fallback allowed
  • require: Require SSL (don’t verify cert)
  • verify-ca: Require SSL, verify CA
  • verify-full: Require SSL, verify hostname + CA

18.3 MySQL/MariaDB with SSL/TLS

Installation

#============================================#
# INSTALL MARIADB (MYSQL REPLACEMENT ON RHEL 8+)
#============================================#

# RHEL 8/9/10
sudo dnf install mariadb-server -y

# Start and enable
sudo systemctl enable mariadb
sudo systemctl start mariadb

# Secure installation
sudo mysql_secure_installation

# Verify
systemctl status mariadb
ss -tlnp | grep 3306

Generate MySQL/MariaDB Certificates

#============================================#
# GENERATE MYSQL/MARIADB CERTIFICATES
#============================================#

# Create certificate directory
sudo mkdir -p /etc/mysql/certs
sudo chmod 755 /etc/mysql/certs

# Step 1: Generate server key
sudo openssl genpkey -algorithm RSA \
  -out /etc/mysql/certs/server.key \
  -pkeyopt rsa_keygen_bits:2048

sudo chmod 600 /etc/mysql/certs/server.key
sudo chown mysql:mysql /etc/mysql/certs/server.key

# Step 2: Generate CSR
sudo openssl req -new \
  -key /etc/mysql/certs/server.key \
  -out /tmp/mysql.csr \
  -subj "/CN=db.example.com" \
  -addext "subjectAltName=DNS:db.example.com,DNS:mysql.example.com"

# Step 3: Get certificate from CA

# Step 4: Install certificate and CA
sudo cp mysql.crt /etc/mysql/certs/server.crt
sudo cp ca.crt /etc/mysql/certs/ca.crt
sudo chmod 644 /etc/mysql/certs/{server.crt,ca.crt}
sudo chown mysql:mysql /etc/mysql/certs/{server.crt,ca.crt}

Configure MySQL/MariaDB for SSL

#============================================#
# CONFIGURE MYSQL/MARIADB SSL
#============================================#

# Edit /etc/my.cnf.d/server.cnf (or /etc/my.cnf)
sudo vi /etc/my.cnf.d/server.cnf

[mysqld]
# SSL/TLS configuration
ssl-ca=/etc/mysql/certs/ca.crt
ssl-cert=/etc/mysql/certs/server.crt
ssl-key=/etc/mysql/certs/server.key

# Require secure transport (optional, forces TLS for all)
require_secure_transport=ON

# RHEL 7: Specify TLS version
# tls_version=TLSv1.2,TLSv1.3

# Restart MySQL/MariaDB
sudo systemctl restart mariadb

Verify SSL is Enabled

#============================================#
# VERIFY MYSQL SSL
#============================================#

# Connect and check SSL status
mysql -u root -p -e "SHOW VARIABLES LIKE '%ssl%';"

# Should show:
# have_ssl           | YES
# ssl_ca             | /etc/mysql/certs/ca.crt
# ssl_cert           | /etc/mysql/certs/server.crt
# ssl_key            | /etc/mysql/certs/server.key

# Check active connections
mysql -u root -p -e "SHOW STATUS LIKE 'Ssl_cipher';"

Test MySQL SSL Connection

#============================================#
# TEST MYSQL SSL CONNECTION
#============================================#

# Connect with SSL
mysql --ssl-ca=/etc/mysql/certs/ca.crt \
  --ssl-mode=REQUIRED \
  -h db.example.com \
  -u testuser \
  -p

# Verify SSL in use
mysql> \s
# Look for: "SSL: Cipher in use is ..."

# Or check from command line
mysql -h db.example.com -u testuser -p -e "STATUS" | grep SSL

18.4 Client Certificate Authentication

PostgreSQL with Client Certificates

#============================================#
# POSTGRESQL CLIENT CERT AUTH
#============================================#

# Server side: pg_hba.conf
hostssl all alice 0.0.0.0/0 cert clientcert=verify-full

# Generate client certificate
openssl genpkey -algorithm RSA -out alice.key -pkeyopt rsa_keygen_bits:2048
openssl req -new -key alice.key -out alice.csr -subj "/CN=alice"
# Get signed by CA

# Client connection
psql "host=db.example.com user=alice dbname=mydb sslmode=verify-full sslcert=alice.crt sslkey=alice.key sslrootcert=ca.crt"

MySQL/MariaDB with Client Certificates

#============================================#
# MYSQL/MARIADB CLIENT CERT AUTH
#============================================#

# Create user requiring X.509
mysql -u root -p << EOF
CREATE USER 'alice'@'%' REQUIRE X509;
GRANT ALL ON mydb.* TO 'alice'@'%';
FLUSH PRIVILEGES;
EOF

# Client connection
mysql --ssl-ca=ca.crt \
  --ssl-cert=alice.crt \
  --ssl-key=alice.key \
  -h db.example.com \
  -u alice \
  -p mydb

18.5 Troubleshooting Database TLS

PostgreSQL Troubleshooting

#============================================#
# POSTGRESQL SSL TROUBLESHOOTING
#============================================#

# Check SSL is enabled
sudo -u postgres psql -c "SHOW ssl;"
# Should show: on

# View SSL settings
sudo -u postgres psql -c "SHOW ssl_cert_file; SHOW ssl_key_file; SHOW ssl_ca_file;"

# Check certificate files
ls -l /var/lib/pgsql/data/server.{crt,key}

# Verify ownership
# Should be: postgres:postgres

# Check permissions
# server.key should be 600

# Test connection with SSL debugging
psql "host=db.example.com sslmode=require" -d postgres -U testuser --set=sslcompression=on

# Check PostgreSQL logs
sudo tail -f /var/lib/pgsql/data/log/postgresql-*.log | grep -i ssl

MySQL/MariaDB Troubleshooting

#============================================#
# MYSQL/MARIADB SSL TROUBLESHOOTING
#============================================#

# Check SSL variables
mysql -u root -p -e "SHOW VARIABLES LIKE '%ssl%';"

# If have_ssl = NO, check:
# 1. Certificate files exist
ls -l /etc/mysql/certs/

# 2. Permissions
# Should be readable by mysql user

# 3. Restart database
sudo systemctl restart mariadb

# Check error log
sudo tail -f /var/log/mariadb/mariadb.log | grep -i ssl

# Test connection
mysql --ssl-ca=/etc/mysql/certs/ca.crt \
  --ssl-mode=REQUIRED \
  -h localhost \
  -u root \
  -p \
  -e "STATUS" | grep SSL

18.6 Common Issues and Solutions

Issue 1: PostgreSQL “Permission denied” on server.key

Symptom: PostgreSQL won’t start, logs show permission error

Fix:

# Set correct permissions
sudo chmod 600 /var/lib/pgsql/data/server.key
sudo chown postgres:postgres /var/lib/pgsql/data/server.key

# Fix SELinux context
sudo restorecon -Rv /var/lib/pgsql/data/

# Restart
sudo systemctl restart postgresql

Issue 2: MySQL “SSL connection error”

Diagnosis:

# Check if SSL is available
mysql -u root -p -e "SHOW VARIABLES LIKE 'have_ssl';"
# Should show: YES

# If shows: DISABLED
# Check certificate paths in my.cnf

Fix:

# Verify paths in /etc/my.cnf.d/server.cnf
[mysqld]
ssl-ca=/etc/mysql/certs/ca.crt
ssl-cert=/etc/mysql/certs/server.crt
ssl-key=/etc/mysql/certs/server.key

# Restart
sudo systemctl restart mariadb

Issue 3: Client Certificate Rejected

Symptom: Connection fails with client cert

PostgreSQL Fix:

# Check pg_hba.conf
cat /var/lib/pgsql/data/pg_hba.conf | grep hostssl

# Ensure client CA is installed
sudo cp client-ca.crt /var/lib/pgsql/data/root.crt

# Reload
sudo systemctl reload postgresql

MySQL Fix:

# Verify user requires X.509
mysql -u root -p -e "SELECT user, host, ssl_type FROM mysql.user WHERE user='alice';"
# Should show: X509

# Verify CA file configured
mysql -u root -p -e "SHOW VARIABLES LIKE 'ssl_ca';"

18.7 Version-Specific Considerations

PostgreSQL Versions on RHEL

RHEL VersionPostgreSQLSSL SupportNotes
RHEL 79.2✅ YesManual TLS version config
RHEL 810.x+✅ YesSystem crypto-policy
RHEL 913.x+✅ YesEnhanced, crypto-policy
RHEL 1015.x+✅ YesLatest, crypto-policy

MySQL/MariaDB Versions on RHEL

RHEL VersionDatabaseSSL SupportNotes
RHEL 7MariaDB 5.5✅ YesManual config
RHEL 8MariaDB 10.3+✅ YesCrypto-policy aware
RHEL 9MariaDB 10.5+✅ YesModern TLS
RHEL 10MariaDB 10.11+✅ YesLatest features

18.8 Performance Considerations

PostgreSQL SSL Performance

#============================================#
# POSTGRESQL SSL PERFORMANCE TUNING
#============================================#

# /var/lib/pgsql/data/postgresql.conf

# SSL enabled
ssl = on

# SSL compression (disabled for security, CRIME attack)
ssl_compression = off

# SSL ciphers (RHEL 7 - manual)
# ssl_ciphers = 'HIGH:!aNULL:!MD5'

# RHEL 8/9/10: crypto-policy handles ciphers

# Connection pooling helps (use pgBouncer)
# SSL termination at proxy can improve performance

MySQL SSL Performance

#============================================#
# MYSQL SSL PERFORMANCE
#============================================#

# [mysqld] in /etc/my.cnf.d/server.cnf

# SSL enabled
ssl-ca=/etc/mysql/certs/ca.crt
ssl-cert=/etc/mysql/certs/server.crt
ssl-key=/etc/mysql/certs/server.key

# Disable weak ciphers (RHEL 7)
# tls_version=TLSv1.2,TLSv1.3
# ssl_cipher='ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256'

# RHEL 8/9/10: crypto-policy handles it

# Connection pooling (use ProxySQL or similar)

18.9 Monitoring Database TLS

PostgreSQL Monitoring

#============================================#
# MONITOR POSTGRESQL SSL
#============================================#

# Check SSL connections
sudo -u postgres psql -c "SELECT datname, usename, ssl, version FROM pg_stat_ssl JOIN pg_stat_activity ON pg_stat_ssl.pid = pg_stat_activity.pid;"

# Count SSL vs non-SSL
sudo -u postgres psql -c "SELECT ssl, COUNT(*) FROM pg_stat_ssl GROUP BY ssl;"

# Check certificate expiration
openssl x509 -in /var/lib/pgsql/data/server.crt -noout -checkend $((86400*30))

# Monitor connections
sudo -u postgres psql -c "SELECT COUNT(*) FROM pg_stat_activity WHERE ssl = true;"

MySQL Monitoring

#============================================#
# MONITOR MYSQL SSL
#============================================#

# Check SSL status
mysql -u root -p -e "SHOW STATUS LIKE 'Ssl%';"

# Count SSL connections
mysql -u root -p -e "SHOW STATUS LIKE 'Ssl_accepts';"

# Current SSL connections
mysql -u root -p -e "SELECT user, host, connection_type FROM information_schema.processlist WHERE connection_type = 'SSL/TLS';"

# Certificate expiration
openssl x509 -in /etc/mysql/certs/server.crt -noout -checkend $((86400*30))

18.10 Complete Setup Scripts

PostgreSQL SSL Setup Script

#!/bin/bash
# setup-postgresql-ssl.sh

echo "=== PostgreSQL SSL Setup ==="

# Generate self-signed cert (replace with proper CA cert!)
sudo -u postgres openssl req -new -x509 -days 365 -nodes \
  -out /var/lib/pgsql/data/server.crt \
  -keyout /var/lib/pgsql/data/server.key \
  -subj "/CN=$(hostname -f)"

# Set permissions
sudo chmod 600 /var/lib/pgsql/data/server.{crt,key}
sudo chown postgres:postgres /var/lib/pgsql/data/server.{crt,key}

# Enable SSL in postgresql.conf
sudo -u postgres psql -c "ALTER SYSTEM SET ssl = on;"

# Restart
sudo systemctl restart postgresql

# Test
sudo -u postgres psql -c "SHOW ssl;"

echo "✅ PostgreSQL SSL enabled"
echo "⚠️ Replace self-signed cert with proper certificate from CA"

18.11 Key Takeaways

  1. Both PostgreSQL and MySQL support SSL/TLS
  2. File ownership critical - postgres:postgres or mysql:mysql
  3. Permissions: 600 for keys, 644 for certs
  4. pg_hba.conf controls PostgreSQL access (hostssl)
  5. sslmode important for PostgreSQL clients
  6. Client certificates enable strong authentication
  7. Test thoroughly before enforcing TLS
  8. Monitor SSL usage - Ensure clients actually use it

Quick Reference Card

┌──────────────────────────────────────────────────────────────┐
│ DATABASE TLS QUICK REFERENCE                                 │
├──────────────────────────────────────────────────────────────┤
│ === POSTGRESQL ===                                           │
│ Config:       /var/lib/pgsql/data/postgresql.conf            │
│ Access:       /var/lib/pgsql/data/pg_hba.conf                │
│ Certs:        /var/lib/pgsql/data/server.{crt,key}           │
│ Owner:        postgres:postgres                              │
│ Enable:       ssl = on                                       │
│ Test:         psql "sslmode=require"                         │
│                                                              │
│ === MYSQL/MARIADB ===                                        │
│ Config:       /etc/my.cnf.d/server.cnf                       │
│ Certs:        /etc/mysql/certs/server.{crt,key}              │
│ Owner:        mysql:mysql                                    │
│ Enable:       ssl-ca, ssl-cert, ssl-key in [mysqld]          │
│ Test:         mysql --ssl-mode=REQUIRED                      │
│                                                              │
│ Permissions:  chmod 600 *.key                                │
│               chmod 644 *.crt                                │
└──────────────────────────────────────────────────────────────┘

⚠️ File ownership and permissions are critical!
✅ Use hostssl in pg_hba.conf to require SSL

🧪 Hands-On Lab

Lab 10: PostgreSQL TLS

Configure TLS for PostgreSQL database connections

  • 📁 Location: labs/en_US/10-postgresql-tls/
  • ⏱️ Time: 25-30 minutes
  • 🎯 Level: Intermediate

Chapter Navigation

Chapter 19: FreeIPA Certificate Services

Enterprise CA: FreeIPA is Red Hat’s integrated identity and certificate management solution. It’s the recommended way to run an internal CA on RHEL.


19.1 What is FreeIPA?

FreeIPA (Identity, Policy, Audit) is an integrated security information management solution combining:

  • 🔐 Identity Management (LDAP directory)
  • 🎫 Authentication (Kerberos)
  • 🔏 Certificate Authority (Dogtag PKI)
  • 📋 Policy Management (sudo, HBAC)
  • 🔍 DNS (BIND integration)

Why FreeIPA for Certificates?

Instead of:

  • ❌ Manually managing certificates per server
  • ❌ External CA costs
  • ❌ Complex PKI infrastructure

FreeIPA Provides:

  • ✅ Internal CA (free!)
  • ✅ Automatic certificate enrollment
  • ✅ Auto-renewal via certmonger
  • ✅ Certificate profiles
  • ✅ Web UI and CLI management
  • ✅ Integration with RHEL services

19.2 FreeIPA Installation (Server)

Prerequisites

#============================================#
# PREREQUISITES FOR FREEIPA SERVER
#============================================#

# Requirements:
# - RHEL 7/8/9/10
# - Minimum 2 GB RAM (4 GB recommended)
# - Fully qualified hostname
# - Proper DNS resolution
# - Static IP address

# Verify hostname
hostnamectl
# Must show FQDN: ipa.example.com

# Verify DNS
nslookup $(hostname -f)

# Set hostname if needed
sudo hostnamectl set-hostname ipa.example.com

Install FreeIPA Server

#============================================#
# INSTALL FREEIPA SERVER
#============================================#

# Install packages
sudo dnf install ipa-server ipa-server-dns -y

# Run installation wizard
sudo ipa-server-install \
  --realm EXAMPLE.COM \
  --domain example.com \
  --ds-password 'DirectoryPassword123!' \
  --admin-password 'AdminPassword123!' \
  --hostname ipa.example.com \
  --setup-dns \
  --forwarder 8.8.8.8 \
  --forwarder 8.8.4.4 \
  --unattended

# Installation takes 5-15 minutes

# Open firewall
sudo firewall-cmd --add-service={http,https,dns,ntp,freeipa-ldap,freeipa-ldaps,freeipa-replication} --permanent
sudo firewall-cmd --reload

# Verify
sudo ipactl status

# Should show multiple services running:
# - Directory Service (389-ds)
# - Certificate Authority (pki-tomcatd)
# - Kerberos KDC
# - Apache Web Server
# - DNS (named)

Access FreeIPA Web UI

# Get Kerberos ticket
kinit admin
# Password: AdminPassword123!

# Access Web UI
# https://ipa.example.com/
# Username: admin
# Password: AdminPassword123!

19.3 Enrolling Clients

Client Installation

#============================================#
# ENROLL CLIENT TO FREEIPA
#============================================#

# On client system (web01.example.com)

# Install IPA client
sudo dnf install ipa-client -y

# Enroll
sudo ipa-client-install \
  --domain example.com \
  --realm EXAMPLE.COM \
  --server ipa.example.com \
  --principal admin \
  --password 'AdminPassword123!' \
  --mkhomedir \
  --unattended

# Verify
sudo ipa-client-install --uninstall  # Just kidding, don't run this!

# Verify enrollment
ipa ping
# Pong!

# Check certificate tracking
sudo getcert list
# Shows certmonger tracking host certificate

19.4 Requesting Certificates from FreeIPA

Method 1: Web UI

  1. Navigate to https://ipa.example.com/
  2. Identity → Hosts → Select host → Actions → New Certificate
  3. Or: Identity → Services → Add service → Request certificate
#============================================#
# REQUEST CERTIFICATE FROM FREEIPA
#============================================#

# For HTTP service on web01
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/web01.crt \
  -k /etc/pki/tls/private/web01.key \
  -K HTTP/web01.example.com@EXAMPLE.COM \
  -D web01.example.com \
  -C "systemctl reload httpd"

# For custom service
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/myapp.crt \
  -k /etc/pki/tls/private/myapp.key \
  -K myapp/web01.example.com@EXAMPLE.COM \
  -D myapp.example.com

# Check status
sudo getcert list

# Wait for MONITORING status (cert issued)

Method 3: ipa cert-request (Advanced)

#============================================#
# ADVANCED: IPA CERT-REQUEST
#============================================#

# Generate CSR
openssl req -new -key server.key -out server.csr \
  -subj "/CN=server.example.com"

# Request certificate via IPA
ipa cert-request server.csr \
  --principal HTTP/server.example.com@EXAMPLE.COM

# Get certificate ID from output
# Certificate: MIIDXTCCAkWgAwIBAgI...
# Request ID: 12345

# Retrieve certificate
ipa cert-show 12345 --out server.crt

19.5 Certificate Profiles

Available Profiles

#============================================#
# FREEIPA CERTIFICATE PROFILES
#============================================#

# List available profiles
ipa certprofile-find

# Common profiles:
# - caIPAserviceCert: Service certificates (HTTP, LDAP, etc.)
# - IECUserRoles: User certificates
# - smimeUserCert: S/MIME email certificates
# - caSelfSignedCert: Self-signed CA

# View profile details
ipa certprofile-show caIPAserviceCert

# Create custom profile
ipa certprofile-import MyCustomProfile \
  --file custom-profile.cfg \
  --store TRUE

Using Specific Profile

# Request with specific profile
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/custom.crt \
  -k /etc/pki/tls/private/custom.key \
  -K HTTP/web01.example.com@EXAMPLE.COM \
  -T caIPAserviceCert  # Specify profile

19.6 Automatic Renewal

How It Works

FreeIPA + certmonger = Automatic Certificate Lifecycle!

#============================================#
# AUTOMATIC RENEWAL WITH FREEIPA
#============================================#

# certmonger automatically:
# 1. Tracks certificate expiration
# 2. Submits renewal request to IPA
# 3. Obtains renewed certificate
# 4. Saves to file
# 5. Runs post-save command (e.g., reload httpd)

# Check renewal status
sudo getcert list

# Example output:
# Request ID '20240101000000':
#   status: MONITORING
#   stuck: no
#   key pair storage: type=FILE,location='/etc/pki/tls/private/web.key'
#   certificate: type=FILE,location='/etc/pki/tls/certs/web.crt'
#   CA: IPA
#   issuer: CN=Certificate Authority,O=EXAMPLE.COM
#   subject: CN=web01.example.com,O=EXAMPLE.COM
#   expires: 2025-01-01 00:00:00 UTC
#   pre-save command:
#   post-save command: systemctl reload httpd
#   track: yes
#   auto-renew: yes

# Renewal happens automatically ~28 days before expiry!

Manual Renewal (If Needed)

# Force renewal now
sudo ipa-getcert resubmit -f /etc/pki/tls/certs/web.crt

# Or by request ID
sudo ipa-getcert resubmit -i 20240101000000

# Check if successful
sudo getcert list -f /etc/pki/tls/certs/web.crt

19.7 FreeIPA as Enterprise CA

CA Certificate Management

#============================================#
# FREEIPA CA MANAGEMENT
#============================================#

# View CA certificate
ipa ca-show ipa

# Export CA certificate
ipa ca-show ipa --certificate --out /tmp/ipa-ca.crt

# Install on clients (automatic during ipa-client-install)
# Manual: Copy to trust store
sudo cp /tmp/ipa-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

# Renew CA certificate (when needed)
sudo ipa-cacert-manage renew

# Check CA expiration
sudo getcert list -d /var/lib/ipa | grep "CA:"

Sub-CAs (Advanced)

#============================================#
# FREEIPA SUB-CA (RHEL 8+)
#============================================#

# Create sub-CA
ipa ca-add subca \
  --subject "CN=SubCA,O=EXAMPLE.COM" \
  --desc "Department Sub-CA"

# Issue certificate from sub-CA
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/dept.crt \
  -k /etc/pki/tls/private/dept.key \
  -X subca \
  -K HTTP/dept.example.com@EXAMPLE.COM

19.8 Service Integration Examples

Apache with FreeIPA Certificates

#============================================#
# APACHE + FREEIPA COMPLETE SETUP
#============================================#

# 1. Enroll system to IPA (if not already)
sudo ipa-client-install

# 2. Request certificate for Apache
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/$(hostname -f).crt \
  -k /etc/pki/tls/private/$(hostname -f).key \
  -K HTTP/$(hostname -f)@EXAMPLE.COM \
  -D $(hostname -f) \
  -C "systemctl reload httpd"

# 3. Wait for certificate
until sudo getcert list -f /etc/pki/tls/certs/$(hostname -f).crt | grep -q "MONITORING"; do
  sleep 5
  echo "Waiting for certificate..."
done

# 4. Configure Apache to use it
# /etc/httpd/conf.d/ssl.conf:
# SSLCertificateFile /etc/pki/tls/certs/$(hostname -f).crt
# SSLCertificateKeyFile /etc/pki/tls/private/$(hostname -f).key

# 5. Reload Apache
sudo systemctl reload httpd

# Certificate auto-renews!

LDAP with FreeIPA Certificates

# FreeIPA's own LDAP service automatically uses IPA certificates
# No manual configuration needed!

# Test
ldapsearch -H ldaps://ipa.example.com:636 -x -b "dc=example,dc=com"

19.9 Troubleshooting FreeIPA Certificates

Common Issues

Issue 1: CA_UNREACHABLE

# Symptom
sudo getcert list
# status: CA_UNREACHABLE

# Diagnosis
# 1. Check IPA server connectivity
ipa ping

# 2. Check Kerberos ticket
klist

# 3. Renew ticket if expired
kinit -k host/$(hostname -f)@EXAMPLE.COM

# 4. Check IPA services
ssh ipa.example.com "sudo ipactl status"

# 5. Retry
sudo ipa-getcert resubmit -i <request-id>

Issue 2: Certificate Request Denied

# Check request status
sudo getcert list -v

# Common causes:
# 1. Service principal doesn't exist
ipa service-find HTTP/$(hostname -f)

# If not found, add it:
ipa service-add HTTP/$(hostname -f)

# 2. Host not enrolled
ipa host-show $(hostname -f)

# 3. Insufficient permissions
# Must request as enrolled host principal

Issue 3: Certificate Not Renewed

# Check certmonger logs
sudo journalctl -u certmonger | tail -50

# Check IPA CA status
sudo ipactl status | grep "CA"

# Force renewal
sudo ipa-getcert resubmit -f /etc/pki/tls/certs/web.crt

# Check CA certificate expiration
sudo openssl x509 -in /etc/ipa/ca.crt -noout -dates

19.10 Advanced Features

IdM ACME Support (RHEL 9+)

FreeIPA can expose its own internal ACME server. This is not Let’s Encrypt.

#============================================#
# ENABLE ACME ON FREEIPA (RHEL 9+)
#============================================#

# On IPA server (RHEL 9+)
sudo ipa-acme-manage enable

# Verify ACME is available
curl https://ipa.example.com/acme/directory

# On client: Use certbot or another ACME client against the IPA ACME directory
sudo certbot register --server https://ipa.example.com/acme/directory
sudo certbot certonly --server https://ipa.example.com/acme/directory \
  -d web01.example.com

# This uses your IdM / FreeIPA CA, not the public Let's Encrypt service

Certificate Hold/Revocation

#============================================#
# REVOKE CERTIFICATES
#============================================#

# Put certificate on hold (temporary)
ipa cert-revoke 12345 --revocation-reason 6

# Revoke permanently
ipa cert-revoke 12345 --revocation-reason 1

# Reasons:
# 0: unspecified
# 1: keyCompromise
# 2: cACompromise
# 4: superseded
# 6: certificateHold (can be removed)

# Remove from hold
ipa cert-remove-hold 12345

# Check revocation status
ipa cert-show 12345

19.11 Monitoring FreeIPA PKI

Health Checks

#============================================#
# FREEIPA PKI HEALTH MONITORING
#============================================#

# Check IPA overall status
sudo ipactl status

# Check CA subsystem
sudo systemctl status pki-tomcatd@pki-tomcat

# Check certificate expirations
ipa-healthcheck --source ipahealthcheck.ipa.certs

# Check certificate tracking
sudo getcert list | grep -E "(Request|status|expires)"

# Monitor CA certificate
openssl x509 -in /etc/ipa/ca.crt -noout -dates

# Check for expiring certificates
ipa cert-find --validnotafter-from=$(date -d '+60 days' +%Y-%m-%d)

19.12 Backup and Recovery

Backup IPA Server

#============================================#
# BACKUP FREEIPA (INCLUDING CA)
#============================================#

# Full backup
sudo ipa-backup --data --online

# Backup location
ls -lh /var/lib/ipa/backup/

# Include CA keys (offline backup only!)
sudo ipactl stop
sudo ipa-backup --data --gpg
# Enter GPG passphrase
sudo ipactl start

Restore IPA Server

# Restore from backup
sudo ipactl stop
sudo ipa-restore /var/lib/ipa/backup/ipa-full-YYYY-MM-DD-HH-MM-SS/
sudo ipactl start

19.13 Best Practices

FreeIPA Certificate Best Practices

✅ Use FreeIPA for all internal certificates
✅ Let certmonger handle renewal (don't manually renew)
✅ Use service principals (HTTP/host, ldap/host, etc.)
✅ Add SANs when requesting certificates
✅ Set post-save commands (-C flag) for service reload
✅ Monitor IPA server health regularly
✅ Backup IPA server weekly (including CA keys)
✅ Have at least 2 IPA replicas (HA)
✅ Monitor CA certificate expiration
✅ Test certificate renewal before expiry
✅ Use certificate profiles for standardization

19.14 Integration Examples

Complete Service Setup with FreeIPA

#!/bin/bash
# setup-service-with-ipa.sh
# Complete workflow for service certificate from FreeIPA

SERVICE_NAME="HTTP"  # Or LDAP, postgresql, etc.
HOST=$(hostname -f)
PRINCIPAL="${SERVICE_NAME}/${HOST}@EXAMPLE.COM"
CERT_FILE="/etc/pki/tls/certs/${HOST}.crt"
KEY_FILE="/etc/pki/tls/private/${HOST}.key"
POST_COMMAND="systemctl reload httpd"

echo "=== Requesting Certificate from FreeIPA ==="

# 1. Ensure service principal exists
if ! ipa service-show "${SERVICE_NAME}/${HOST}" &>/dev/null; then
  echo "Creating service principal..."
  ipa service-add "${SERVICE_NAME}/${HOST}"
fi

# 2. Request certificate
sudo ipa-getcert request \
  -f "$CERT_FILE" \
  -k "$KEY_FILE" \
  -K "$PRINCIPAL" \
  -D "$HOST" \
  -C "$POST_COMMAND"

# 3. Wait for certificate
echo "Waiting for certificate issuance..."
until sudo getcert list -f "$CERT_FILE" | grep -q "MONITORING"; do
  sleep 5
done

# 4. Verify
echo "✅ Certificate issued!"
sudo openssl x509 -in "$CERT_FILE" -noout -subject -issuer -dates

# 5. Certificate will auto-renew!
echo "✅ Certificate tracking enabled - auto-renewal active"

19.15 Key Takeaways

  1. FreeIPA is Red Hat’s recommended internal CA
  2. Combines identity + certificates + authentication
  3. certmonger integration is automatic
  4. Certificates auto-renew (no manual work!)
  5. Use service principals (HTTP/host, ldap/host)
  6. IdM ACME can expose an internal ACME endpoint in RHEL 9+; keep it distinct from Let’s Encrypt
  7. Web UI and CLI both available
  8. Scales to enterprise - Supports replicas, sub-CAs

Quick Reference Card

┌──────────────────────────────────────────────────────────────┐
│ FREEIPA CERTIFICATE SERVICES QUICK REFERENCE                 │
├──────────────────────────────────────────────────────────────┤
│ Install:       dnf install ipa-server                        │
│ Setup:         ipa-server-install                            │
│ Status:        ipactl status                                 │
│ Web UI:        https://ipa.example.com/                      │
│                                                              │
│ Enroll:        ipa-client-install                            │
│ Request:       ipa-getcert request -K service/host@REALM     │
│ List:          getcert list                                  │
│ Resubmit:      ipa-getcert resubmit -f /path/to/cert.crt     │
│                                                              │
│ Principal:     HTTP/host.example.com@REALM                   │
│                ldap/host.example.com@REALM                   │
│                postgresql/host.example.com@REALM             │
│                                                              │
│ Auto-renewal:  Automatic via certmonger                      │
│ ACME:          ipa-acme-manage enable (RHEL 9+)              │
└──────────────────────────────────────────────────────────────┘

✅ Best for internal enterprise certificate management
✅ Fully integrated with RHEL
✅ No manual renewal needed!

Chapter Navigation

Chapter 20: Other RHEL Services with Certificates

Beyond the Basics: Many other RHEL services use certificates. This chapter covers Cockpit, VPN services, container registries, and more.


20.1 Services Covered

This chapter provides quick-start guides for:

  • 🖥️ Cockpit (Web-based admin console)
  • 🔒 OpenVPN (VPN service)
  • 🛡️ strongSwan (IPsec VPN)
  • 📦 Container Registry (Podman/Docker registry)
  • 📡 HAProxy (Load balancer)
  • 🔌 Redis with TLS (using stunnel)
  • ⚙️ Ansible Tower/AWX (Automation platform)

20.2 Cockpit Web Console

What is Cockpit?

Cockpit is RHEL’s built-in web-based administration interface.

Default: Uses self-signed certificate Goal: Replace with proper certificate

Configure Cockpit with Certificates

#============================================#
# COCKPIT WITH PROPER CERTIFICATE
#============================================#

# Install Cockpit
sudo dnf install cockpit -y
sudo systemctl enable --now cockpit.socket

# Open firewall
sudo firewall-cmd --add-service=cockpit --permanent
sudo firewall-cmd --reload

# Cockpit certificate location
ls -l /etc/cockpit/ws-certs.d/

# Method 1: Place certificate with specific name
# Cockpit uses certificates in /etc/cockpit/ws-certs.d/
# Filename format: NN-name.cert (where NN = priority, lower = higher priority)

sudo cat server.crt server.key > /etc/cockpit/ws-certs.d/01-server.cert
sudo chmod 644 /etc/cockpit/ws-certs.d/01-server.cert

# Restart Cockpit
sudo systemctl restart cockpit.socket

# Method 2: Use certmonger
sudo ipa-getcert request \
  -f /etc/cockpit/ws-certs.d/01-cockpit.cert \
  -k /etc/cockpit/ws-certs.d/01-cockpit.cert \
  -K HTTP/$(hostname -f)@REALM \
  -D $(hostname -f) \
  -C "systemctl restart cockpit.socket" \
  -F /etc/cockpit/ws-certs.d/01-cockpit.cert  # Combined cert+key

# Access Cockpit
# https://server.example.com:9090/

Note: Cockpit expects combined cert+key in single file!


20.3 OpenVPN

Server Configuration with Certificates

#============================================#
# OPENVPN SERVER WITH CERTIFICATES
#============================================#

# Install OpenVPN (from EPEL on RHEL 7, repos on RHEL 8+)
sudo dnf install openvpn -y

# Generate or obtain certificates:
# - CA certificate
# - Server certificate + key
# - Client certificates

# /etc/openvpn/server/server.conf
port 1194
proto udp
dev tun

ca /etc/openvpn/server/ca.crt
cert /etc/openvpn/server/server.crt
key /etc/openvpn/server/server.key
dh /etc/openvpn/server/dh2048.pem

tls-auth /etc/openvpn/server/ta.key 0
cipher AES-256-GCM
auth SHA256

server 10.8.0.0 255.255.255.0

# Start OpenVPN
sudo systemctl enable --now openvpn-server@server

# Open firewall
sudo firewall-cmd --add-port=1194/udp --permanent
sudo firewall-cmd --reload

Test OpenVPN

# Check if running
systemctl status openvpn-server@server

# Test from client
openvpn --config client.ovpn --verb 3

20.4 strongSwan IPsec VPN

Configure with Certificates

#============================================#
# STRONGSWAN WITH CERTIFICATES
#============================================#

# Install
sudo dnf install strongswan -y

# Certificate locations
# CA: /etc/strongswan/ipsec.d/cacerts/
# Server/Client certs: /etc/strongswan/ipsec.d/certs/
# Private keys: /etc/strongswan/ipsec.d/private/

# Copy certificates
sudo cp ca.crt /etc/strongswan/ipsec.d/cacerts/
sudo cp server.crt /etc/strongswan/ipsec.d/certs/
sudo cp server.key /etc/strongswan/ipsec.d/private/
sudo chmod 600 /etc/strongswan/ipsec.d/private/server.key

# /etc/strongswan/ipsec.conf
config setup
    charondebug="ike 2, knl 2, cfg 2"

conn example-ipsec
    left=%any
    leftid=@server.example.com
    leftcert=server.crt
    leftsubnet=10.0.0.0/24

    right=%any
    rightid=@client.example.com
    rightcert=client.crt

    auto=add
    type=tunnel
    keyexchange=ikev2

# Start strongSwan
sudo systemctl enable --now strongswan

# Check status
sudo swanctl --list-sas

20.5 Container Registry with TLS

Podman/Docker Registry

#============================================#
# CONTAINER REGISTRY WITH TLS
#============================================#

# Install registry
sudo dnf install -y podman
sudo podman pull docker.io/library/registry:2

# Create certificate for registry
sudo mkdir -p /etc/registry/certs
sudo openssl genpkey -algorithm RSA \
  -out /etc/registry/certs/registry.key \
  -pkeyopt rsa_keygen_bits:2048

sudo openssl req -new -x509 -days 365 \
  -key /etc/registry/certs/registry.key \
  -out /etc/registry/certs/registry.crt \
  -subj "/CN=registry.example.com" \
  -addext "subjectAltName=DNS:registry.example.com"

# Run registry with TLS
sudo podman run -d \
  --name registry \
  -p 5000:5000 \
  -v /etc/registry/certs:/certs:ro \
  -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry.crt \
  -e REGISTRY_HTTP_TLS_KEY=/certs/registry.key \
  registry:2

# Test
curl https://registry.example.com:5000/v2/_catalog

Client Configuration

# Add registry CA to system trust
sudo cp /etc/registry/certs/registry.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

# Test pull
podman pull registry.example.com:5000/myimage:latest

20.6 HAProxy TLS Termination

Load Balancer with TLS

#============================================#
# HAPROXY TLS TERMINATION
#============================================#

# Install HAProxy
sudo dnf install haproxy -y

# HAProxy requires combined cert+key+chain in ONE file
cat server.crt intermediate.crt server.key > /etc/haproxy/certs/bundle.pem
sudo chmod 600 /etc/haproxy/certs/bundle.pem

# /etc/haproxy/haproxy.cfg
frontend https-in
    bind *:443 ssl crt /etc/haproxy/certs/bundle.pem
    mode http
    default_backend web_servers

    # Force HTTPS
    http-request redirect scheme https unless { ssl_fc }

    # Security headers
    http-response set-header Strict-Transport-Security "max-age=31536000"

backend web_servers
    mode http
    balance roundrobin
    server web1 10.0.1.10:80 check
    server web2 10.0.1.11:80 check
    server web3 10.0.1.12:80 check

# Start HAProxy
sudo systemctl enable --now haproxy

# Test
curl -v https://loadbalancer.example.com/

20.7 Redis with TLS (via stunnel)

Redis TLS Proxy

#============================================#
# REDIS WITH TLS USING STUNNEL
#============================================#

# Install Redis and stunnel
sudo dnf install redis stunnel -y

# Configure Redis (listen on localhost only)
# /etc/redis/redis.conf
bind 127.0.0.1

# Start Redis
sudo systemctl enable --now redis

# Configure stunnel
# /etc/stunnel/redis.conf
[redis-tls]
accept = 0.0.0.0:6380
connect = 127.0.0.1:6379
cert = /etc/pki/tls/certs/redis.crt
key = /etc/pki/tls/private/redis.key
CAfile = /etc/pki/tls/certs/ca-bundle.crt

# Optional: Require client certificates
verify = 2
CApath = /etc/pki/tls/certs/

# Start stunnel
sudo systemctl enable --now stunnel@redis

# Test
openssl s_client -connect localhost:6380
# Then type: PING
# Should respond: +PONG

20.8 Ansible Tower/AWX

Tower/AWX with Custom Certificate

#============================================#
# ANSIBLE TOWER/AWX CUSTOM CERTIFICATE
#============================================#

# Tower certificate location
# /etc/tower/tower.cert
# /etc/tower/tower.key

# Replace with proper certificate
sudo cp tower.example.com.crt /etc/tower/tower.cert
sudo cp tower.example.com.key /etc/tower/tower.key
sudo chmod 600 /etc/tower/tower.key

# Restart Tower services
sudo ansible-tower-service restart

# Or for AWX (containerized)
# Update docker-compose.yml or k8s secrets

# Test
curl -v https://tower.example.com/

20.9 SSH with Certificates (Advanced)

SSH Certificate Authentication

Note: Different from SSH keys! This uses X.509 certificates.

#============================================#
# SSH WITH X.509 CERTIFICATES (ADVANCED)
#============================================#

# Requires: openssh-server with X.509 patch or ssh-keysign

# Generate certificate for SSH user
openssl genpkey -algorithm RSA -out ssh-user.key -pkeyopt rsa_keygen_bits:2048
openssl req -new -key ssh-user.key -out ssh-user.csr -subj "/CN=user@example.com"
# Get signed by CA

# Configure sshd (experimental, not standard RHEL)
# /etc/ssh/sshd_config
# X509KeyAlgorithm x509v3-rsa2048-sha256
# X509TrustAnchor /etc/ssh/ca.crt

# Standard approach: Use SSH keys, not X.509
# X.509 SSH support is limited on RHEL

Recommendation: Use standard SSH key-based authentication for SSH, use X.509 for other services.


20.10 Monitoring Multiple Services

Multi-Service Certificate Check

#!/bin/bash
# check-all-services.sh
# Check certificates for all services

echo "=== Multi-Service Certificate Check ==="

# Apache
echo "1. Apache (port 443):"
timeout 3 openssl s_client -connect localhost:443 </dev/null 2>&1 | grep -E "(subject=|issuer=)" | head -2

# NGINX (if installed)
echo "2. NGINX (port 8443 or custom):"
timeout 3 openssl s_client -connect localhost:8443 </dev/null 2>&1 | grep -E "(subject=|issuer=)" | head -2

# Postfix
echo "3. Postfix SMTPS (port 465):"
timeout 3 openssl s_client -connect localhost:465 </dev/null 2>&1 | grep -E "(subject=|issuer=)" | head -2

# LDAPS
echo "4. LDAP (port 636):"
timeout 3 openssl s_client -connect localhost:636 </dev/null 2>&1 | grep -E "(subject=|issuer=)" | head -2

# PostgreSQL
echo "5. PostgreSQL (port 5432):"
sudo -u postgres psql -c "SHOW ssl;" 2>/dev/null

# Cockpit
echo "6. Cockpit (port 9090):"
timeout 3 openssl s_client -connect localhost:9090 </dev/null 2>&1 | grep -E "(subject=|issuer=)" | head -2

# certmonger tracking
echo ""
echo "7. certmonger tracked certificates:"
sudo getcert list | grep -c "Request ID"
echo "total certificates tracked"

echo ""
echo "=== Check Complete ==="

20.11 Service-Specific Quick References

Cockpit

# Install: dnf install cockpit
# Cert location: /etc/cockpit/ws-certs.d/
# Format: Combined cert+key
# Reload: systemctl restart cockpit.socket
# Test: https://server:9090/

OpenVPN

# Install: dnf install openvpn (EPEL)
# Cert location: /etc/openvpn/server/
# Files: ca.crt, server.crt, server.key
# Start: systemctl start openvpn-server@server
# Test: openvpn --config client.ovpn

strongSwan

# Install: dnf install strongswan
# Cert location: /etc/strongswan/ipsec.d/
# Subdirs: cacerts/, certs/, private/
# Start: systemctl start strongswan
# Test: swanctl --list-sas

HAProxy

# Install: dnf install haproxy
# Cert format: Combined PEM (cert+key+chain)
# Location: /etc/haproxy/certs/
# Config: bind *:443 ssl crt /path/to/bundle.pem
# Test: curl -v https://loadbalancer/

Container Registry

# Run: podman run -d -p 5000:5000 registry:2
# Certs: Mount as volumes (-v)
# Env vars: REGISTRY_HTTP_TLS_CERTIFICATE
#           REGISTRY_HTTP_TLS_KEY
# Test: curl https://registry:5000/v2/_catalog

20.12 Wildcard Certificates for Multiple Services

When to Use Wildcards

Scenario: Multiple subdomains on same server

web.example.com    → Apache
api.example.com    → NGINX
admin.example.com  → Cockpit
mail.example.com   → Postfix

Solution: Use wildcard certificate *.example.com

Generate Wildcard Certificate

#============================================#
# WILDCARD CERTIFICATE
#============================================#

# Generate key
openssl genpkey -algorithm RSA -out wildcard.key -pkeyopt rsa_keygen_bits:2048

# Generate CSR
openssl req -new -key wildcard.key -out wildcard.csr \
  -subj "/CN=*.example.com" \
  -addext "subjectAltName=DNS:*.example.com,DNS:example.com"

# Submit to CA, receive wildcard.crt

# Use for multiple services
sudo cp wildcard.crt /etc/pki/tls/certs/
sudo cp wildcard.key /etc/pki/tls/private/
sudo chmod 600 /etc/pki/tls/private/wildcard.key

# Configure each service to use it
# Apache: SSLCertificateFile /etc/pki/tls/certs/wildcard.crt
# NGINX: ssl_certificate /etc/pki/tls/certs/wildcard.crt
# Postfix: smtpd_tls_cert_file = /etc/pki/tls/certs/wildcard.crt

Pros:

  • ✅ One certificate for multiple subdomains
  • ✅ Easier management
  • ✅ Cost-effective (if purchasing)

Cons:

  • ⚠️ If compromised, affects all subdomains
  • ⚠️ Doesn’t work for multi-level (..example.com)
  • ⚠️ Some security policies prohibit wildcards

20.13 Service Certificate Matrix

Certificate Requirements by Service

ServiceCN/SANClient CertAuto-RenewSpecial Notes
ApacheRequiredOptional (mTLS)certmongerMost common
NGINXRequiredOptional (mTLS)certmongerHigh performance
PostfixRequiredOptionalcertmongerSMTP/SMTPS
OpenLDAPRequiredOptionalcertmongerMust be ldap user readable
PostgreSQLRequiredOptionalManual or scriptpostgres user ownership
MySQLRequiredOptionalManual or scriptmysql user ownership
FreeIPAAutomaticN/AAutomaticSelf-managing
CockpitRequiredNocertmongerCombined cert+key file
OpenVPNRequiredRequiredManualComplex PKI
strongSwanRequiredRequiredManualIPsec specific
HAProxyRequiredNocertmongerCombined PEM format
RegistryRequiredOptionalManualContainer specific

20.14 Troubleshooting Quick Guide

Generic Service TLS Troubleshooting

#============================================#
# UNIVERSAL TLS TROUBLESHOOTING
#============================================#

# 1. Identify service and port
ss -tlnp | grep <service>

# 2. Check if TLS is enabled
# (service-specific command)

# 3. Test TLS connection
openssl s_client -connect localhost:<port>
# Or with STARTTLS:
openssl s_client -connect localhost:<port> -starttls <protocol>

# 4. Check certificate files
ls -lZ /path/to/certs/

# 5. Verify ownership/permissions
# - Certificate: 644, owned by service user
# - Key: 600, owned by service user

# 6. Check configuration
# (service-specific config file)

# 7. Check logs
sudo journalctl -u <service> | grep -i tls
sudo tail -f /var/log/<service>/ | grep -i tls

# 8. Test from remote client
openssl s_client -connect server.example.com:<port>

20.15 Centralized Certificate Management

Using certmonger for All Services

#============================================#
# CENTRAL CERTIFICATE MANAGEMENT STRATEGY
#============================================#

# Track all service certificates with certmonger

# Apache
sudo ipa-getcert request -f /etc/pki/tls/certs/apache.crt \
  -k /etc/pki/tls/private/apache.key \
  -K HTTP/$(hostname -f)@REALM \
  -C "systemctl reload httpd"

# NGINX
sudo ipa-getcert request -f /etc/pki/tls/certs/nginx.crt \
  -k /etc/pki/tls/private/nginx.key \
  -K HTTP/$(hostname -f)@REALM \
  -C "systemctl reload nginx"

# Postfix
sudo ipa-getcert request -f /etc/pki/tls/certs/postfix.crt \
  -k /etc/pki/tls/private/postfix.key \
  -K smtp/$(hostname -f)@REALM \
  -C "postfix reload"

# OpenLDAP
sudo ipa-getcert request -f /etc/openldap/certs/ldap.crt \
  -k /etc/openldap/certs/ldap.key \
  -K ldap/$(hostname -f)@REALM \
  -o ldap:ldap \
  -m 600 \
  -C "systemctl restart slapd"

# Monitor all
sudo getcert list

Benefits:

  • ✅ Single tool for all services
  • ✅ Automatic renewal
  • ✅ Centralized monitoring
  • ✅ Consistent approach

20.16 Key Takeaways

  1. Many services use certificates beyond just web servers
  2. Each service has unique requirements - Check ownership, permissions
  3. certmonger works with most services for automation
  4. Wildcard certificates can simplify multi-service setups
  5. Test each service independently
  6. Centralized tracking with certmonger recommended
  7. Document service-specific configurations

Quick Reference Card

┌───────────────────────────────────────────────────────────────┐
│ OTHER SERVICES CERTIFICATE QUICK REFERENCE                    │
├───────────────────────────────────────────────────────────────┤
│ Cockpit:     /etc/cockpit/ws-certs.d/NN-name.cert             │
│              (combined cert+key)                              │
│                                                               │
│ OpenVPN:     /etc/openvpn/server/{ca,server}.{crt,key}        │
│              Complex PKI with client certs                    │
│                                                               │
│ strongSwan:  /etc/strongswan/ipsec.d/{cacerts,certs,private}/ │
│              IPsec-specific configuration                     │
│                                                               │
│ HAProxy:     Combined PEM (cert+key+chain in one file)        │
│              /etc/haproxy/certs/bundle.pem                    │
│                                                               │
│ Registry:    Environment variables for container              │
│              REGISTRY_HTTP_TLS_CERTIFICATE/KEY                │
│                                                               │
│ Generic:     Check ownership, permissions, SELinux            │
│              Test with: openssl s_client -connect :port       │
└───────────────────────────────────────────────────────────────┘

✅ Use certmonger for automation where possible
✅ Each service has unique file format/location requirements

Chapter Navigation

Chapter 21: Service Certificate Best Practices

Critical for Operations: Learn the best practices that prevent 90% of certificate problems before they happen.


21.1 The Cost of Poor Certificate Management

Real-world impacts:

  • ❌ Expired certificate → Website down (revenue loss)
  • ❌ Wrong permissions → Service fails to start (downtime)
  • ❌ No backup → CA failure means manual reissue (hours/days)
  • ❌ Poor naming → Confusion during incident (delayed response)
  • ❌ No monitoring → Surprise expiration (emergency response)

This chapter prevents these issues.


21.2 File Organization Best Practices

Standard Directory Structure

/etc/pki/tls/
├── certs/                      # Certificate files (public)
│   ├── service-name.crt        # Actual certificates
│   ├── service-name-chain.crt  # With intermediate chain
│   └── ca-bundle.crt           # CA bundle
│
├── private/                    # Private keys (protected!)
│   └── service-name.key        # Private keys (mode 600)
│
├── csr/                        # Certificate requests (optional)
│   └── service-name.csr        # CSRs for tracking
│
└── backup/                     # Backups (optional but recommended)
    └── YYYY-MM-DD/
        ├── service-name.crt
        └── service-name.key

Naming Conventions

Good naming prevents confusion:

# ✅ GOOD - Clear, descriptive
/etc/pki/tls/certs/web01-example-com.crt
/etc/pki/tls/certs/mail-smtp-example-com.crt
/etc/pki/tls/certs/ldap-primary-example-com.crt

# ❌ BAD - Unclear, generic
/etc/pki/tls/certs/cert1.crt
/etc/pki/tls/certs/new.crt
/etc/pki/tls/certs/temp.crt

Naming pattern:

[service]-[hostname/function]-[domain].crt
[service]-[hostname/function]-[domain].key

Examples:
apache-web01-example-com.crt
nginx-www-example-com.crt
postfix-mail-example-com.crt
ldap-dir01-example-com.crt
postgresql-db-primary-example-com.crt

File Permission Standards

#============================================#
# CRITICAL: Proper Permissions
#============================================#

# Certificates (public) - readable by all
/etc/pki/tls/certs/*.crt           → 644 (rw-r--r--)
/etc/pki/tls/certs/                → 755 (rwxr-xr-x)

# Private keys (secret!) - only readable by owner
/etc/pki/tls/private/*.key         → 600 (rw-------)
/etc/pki/tls/private/              → 711 (rwx--x--x)

# Service-specific keys - owned by service user
/etc/pki/tls/private/apache.key    → 600, owner: root or apache
/etc/pki/tls/private/postgres.key  → 600, owner: postgres

Set permissions script:

#!/bin/bash
# set-cert-permissions.sh
# Sets proper permissions on certificate files

CERT_DIR="/etc/pki/tls/certs"
KEY_DIR="/etc/pki/tls/private"

# Certificate directory
chmod 755 "$CERT_DIR"
chmod 644 "$CERT_DIR"/*.crt 2>/dev/null

# Private key directory
chmod 711 "$KEY_DIR"
chmod 600 "$KEY_DIR"/*.key 2>/dev/null

# Verify
echo "Certificate permissions:"
ls -ld "$CERT_DIR" "$CERT_DIR"/*.crt 2>/dev/null

echo ""
echo "Private key permissions:"
ls -ld "$KEY_DIR" "$KEY_DIR"/*.key 2>/dev/null

# Check for overly permissive keys
echo ""
echo "Checking for security issues:"
find "$KEY_DIR" -type f -not -perm 600 -ls 2>/dev/null && \
  echo "⚠️ WARNING: Some keys have incorrect permissions!" || \
  echo "✅ All keys properly protected"

21.3 Certificate Lifecycle Management

Renewal Timeline

Certificate Lifecycle (365 day validity):

Day   0: Certificate issued
Day  30: First renewal reminder (335 days left)
Day  60: Second reminder (305 days left)
Day 300: Critical renewal window starts (65 days left)
Day 330: URGENT - Renewal needed (35 days left)
Day 350: CRITICAL - Renewal overdue (15 days left)
Day 365: EXPIRED - Service outage!

Recommended Actions:
- Days 300-330: Plan and execute renewal
- Days 330-350: Emergency renewal if missed
- Days 350+: Incident response, temporary cert

Renewal Strategies

Strategy 1: Automated (Recommended)

# Using certmonger (RHEL)
sudo getcert request \
  -f /etc/pki/tls/certs/web01-example-com.crt \
  -k /etc/pki/tls/private/web01-example-com.key \
  -D web.example.com \
  -K host/web.example.com@REALM \
  -C "systemctl reload httpd"  # Auto-reload service

# Auto-renewal happens at 2/3 of cert lifetime
# 365 day cert → renews at day 243 (122 days remaining)

Strategy 2: Scheduled Manual Renewal

# Cron job for manual renewal check
# /etc/cron.weekly/check-certificates

#!/bin/bash
# Check certificates expiring in 60 days
find /etc/pki/tls/certs/ -name "*.crt" | while read cert; do
  if openssl x509 -in "$cert" -noout -checkend $((86400*60)); then
    echo "✅ $cert: OK"
  else
    echo "⚠️ $cert: Expires within 60 days!"
    # Send alert
    mail -s "Certificate Expiring Soon: $cert" admin@example.com
  fi
done

Strategy 3: Calendar Reminders

# For environments without automation
# Create calendar entries:
# - 90 days before expiration: Start renewal
# - 60 days before: Verify renewal in progress
# - 30 days before: Complete renewal
# - 7 days before: Emergency if not done

21.4 Certificate Metadata Tracking

Certificate Inventory

Maintain a certificate inventory (spreadsheet or database):

Service,Hostname,Certificate_Path,Key_Path,Issuer,Issue_Date,Expiry_Date,SANs,Owner,Notes
Apache,web01,/etc/pki/tls/certs/web01-example-com.crt,/etc/pki/tls/private/web01.key,Internal CA,2024-01-01,2025-01-01,"web01.example.com,www.example.com",John Doe,Production
NGINX,web02,/etc/pki/tls/certs/web02-example-com.crt,/etc/pki/tls/private/web02.key,Let's Encrypt,2024-06-15,2024-09-15,"web02.example.com",Jane Smith,Staging

Generate inventory script:

#!/bin/bash
# generate-cert-inventory.sh
# Creates certificate inventory from system

echo "Service,Hostname,Certificate_Path,Issuer,Issue_Date,Expiry_Date,Days_Remaining"

# Scan common certificate locations
for cert in /etc/pki/tls/certs/*.crt /etc/httpd/conf/ssl/*.crt /etc/nginx/ssl/*.crt; do
  [ -f "$cert" ] || continue

  subject=$(openssl x509 -in "$cert" -noout -subject 2>/dev/null | sed 's/subject=//')
  issuer=$(openssl x509 -in "$cert" -noout -issuer 2>/dev/null | sed 's/issuer=//')
  notbefore=$(openssl x509 -in "$cert" -noout -startdate 2>/dev/null | cut -d= -f2)
  notafter=$(openssl x509 -in "$cert" -noout -enddate 2>/dev/null | cut -d= -f2)

  # Calculate days remaining
  expiry_epoch=$(date -d "$notafter" +%s 2>/dev/null)
  now_epoch=$(date +%s)
  days_remaining=$(( ($expiry_epoch - $now_epoch) / 86400 ))

  # Determine service from path
  service="Unknown"
  [[ "$cert" =~ httpd ]] && service="Apache"
  [[ "$cert" =~ nginx ]] && service="NGINX"

  echo "$service,$(hostname),$cert,\"$issuer\",$notbefore,$notafter,$days_remaining"
done

21.5 Backup and Recovery

What to Backup

Critical files to backup:
✅ Private keys (.key files)
✅ Certificates (.crt files)
✅ CA certificates
✅ Certificate chains
✅ CSRs (for reference)
✅ Configuration files (Apache ssl.conf, etc.)
⚠️ NOT passwords or passphrases (store separately in vault)

Backup Script

#!/bin/bash
# backup-certificates.sh
# Backs up all certificates and keys

BACKUP_DIR="/var/backups/certificates"
DATE=$(date +%Y-%m-%d)
BACKUP_PATH="$BACKUP_DIR/$DATE"

# Create backup directory
mkdir -p "$BACKUP_PATH"

# Backup certificates
echo "Backing up certificates..."
cp -a /etc/pki/tls/certs/*.crt "$BACKUP_PATH/" 2>/dev/null

# Backup private keys (encrypted!)
echo "Backing up private keys..."
tar czf - /etc/pki/tls/private/*.key 2>/dev/null | \
  openssl enc -aes-256-cbc -salt -out "$BACKUP_PATH/keys.tar.gz.enc" -pass pass:CHANGEME

# Backup configuration files
echo "Backing up configs..."
cp -a /etc/httpd/conf.d/ssl.conf "$BACKUP_PATH/" 2>/dev/null
cp -a /etc/nginx/nginx.conf "$BACKUP_PATH/" 2>/dev/null

# Create inventory
ls -lh "$BACKUP_PATH"

# Set permissions
chmod 700 "$BACKUP_PATH"

echo "✅ Backup complete: $BACKUP_PATH"
echo "⚠️ Remember to change encryption password!"

Recovery Procedure

#============================================#
# CERTIFICATE RECOVERY PROCEDURE
#============================================#

# 1. Stop affected service
sudo systemctl stop httpd

# 2. Restore certificate
sudo cp /var/backups/certificates/2024-11-15/web.crt /etc/pki/tls/certs/

# 3. Restore private key (decrypt)
cd /var/backups/certificates/2024-11-15/
openssl enc -aes-256-cbc -d -in keys.tar.gz.enc -pass pass:CHANGEME | \
  sudo tar xzf - -C /

# 4. Set permissions
sudo chmod 600 /etc/pki/tls/private/*.key
sudo chmod 644 /etc/pki/tls/certs/*.crt

# 5. Verify files
sudo openssl x509 -in /etc/pki/tls/certs/web-example-com.crt -noout -text
sudo openssl rsa -in /etc/pki/tls/private/web-example-com.key -check

# 6. Start service
sudo systemctl start httpd

# 7. Test
curl -v https://localhost/

21.6 Security Best Practices

Private Key Protection

#============================================#
# PRIVATE KEY SECURITY CHECKLIST
#============================================#

✅ Permissions: 600 (or 400 for extra protection)
✅ Ownership: root or service user only
✅ Location: /etc/pki/tls/private/ (mode 711)
✅ SELinux: Proper context (cert_t)
✅ Backup: Encrypted at rest
✅ Never: Email, paste in tickets, commit to git
✅ Never: Share between systems (generate new)
✅ Audit: Log access with auditd

# Verify security
ls -lZ /etc/pki/tls/private/*.key
# -rw------- root root unconfined_u:object_r:cert_t:s0 server.key

Key Generation Best Practices

#============================================#
# GENERATE SECURE KEYS
#============================================#

# RSA 2048 (minimum for RHEL 8+)
openssl genpkey -algorithm RSA -out server.key -pkeyopt rsa_keygen_bits:2048

# RSA 4096 (recommended for long-lived certs)
openssl genpkey -algorithm RSA -out server.key -pkeyopt rsa_keygen_bits:4096

# EC P-256 (modern, smaller, fast)
openssl genpkey -algorithm EC -out server.key -pkeyopt ec_paramgen_curve:P-256

# Immediately set permissions!
chmod 600 server.key

# ❌ NEVER do this:
# openssl genrsa -out server.key 1024   # Too weak!
# chmod 644 server.key                  # Too permissive!

Certificate Validation Before Deployment

#!/bin/bash
# validate-certificate.sh
# Validates certificate before deployment

CERT=$1
KEY=$2

echo "=== Pre-Deployment Certificate Validation ==="

# Check 1: Certificate file exists and readable
if [ ! -f "$CERT" ]; then
  echo "❌ Certificate file not found: $CERT"
  exit 1
fi

# Check 2: Private key exists and readable
if [ ! -f "$KEY" ]; then
  echo "❌ Private key not found: $KEY"
  exit 1
fi

# Check 3: Certificate is valid X.509
if ! openssl x509 -in "$CERT" -noout 2>/dev/null; then
  echo "❌ Invalid X.509 certificate"
  exit 1
fi

# Check 4: Certificate not expired
if ! openssl x509 -in "$CERT" -noout -checkend 0; then
  echo "❌ Certificate is expired!"
  exit 1
fi

# Check 5: Certificate/key pair match
CERT_MOD=$(openssl x509 -noout -modulus -in "$CERT" | openssl md5)
KEY_MOD=$(openssl rsa -noout -modulus -in "$KEY" 2>/dev/null | openssl md5)

if [ "$CERT_MOD" != "$KEY_MOD" ]; then
  echo "❌ Certificate and key do not match!"
  exit 1
fi

# Check 6: SANs present (required for modern browsers)
if ! openssl x509 -in "$CERT" -noout -ext subjectAltName 2>/dev/null | grep -q "DNS:"; then
  echo "⚠️ WARNING: No Subject Alternative Names found"
fi

# Check 7: Strong signature algorithm
SIG_ALG=$(openssl x509 -in "$CERT" -noout -text | grep "Signature Algorithm" | head -2)
if echo "$SIG_ALG" | grep -qi "sha1\|md5"; then
  echo "❌ Weak signature algorithm: $SIG_ALG"
  exit 1
fi

# Check 8: Adequate key size
KEY_SIZE=$(openssl x509 -in "$CERT" -noout -text | grep "Public-Key:" | grep -oP '\d+')
if [ "$KEY_SIZE" -lt 2048 ]; then
  echo "❌ Key size too small: $KEY_SIZE bits (minimum 2048)"
  exit 1
fi

echo ""
echo "✅ Certificate validation passed!"
echo "   Subject: $(openssl x509 -in "$CERT" -noout -subject)"
echo "   Issuer: $(openssl x509 -in "$CERT" -noout -issuer)"
echo "   Expires: $(openssl x509 -in "$CERT" -noout -enddate | cut -d= -f2)"
echo "   Key Size: $KEY_SIZE bits"

21.7 Multi-Service Coordination

When Multiple Services Share Certificates

# Scenario: Load balancer + multiple web servers

# Problem: Certificate on LB, services behind need same CN/SANs

# Solution 1: Use same certificate on all (if hostnames match)
# web01, web02, web03 all use cert for: web.example.com

# Solution 2: Wildcard certificate
# *.example.com works for web01.example.com, web02.example.com, etc.

# Solution 3: Comprehensive SANs
# Single cert with SANs: web.example.com, web01.example.com, web02.example.com

Certificate Deployment Workflow

#============================================#
# MULTI-SERVER DEPLOYMENT
#============================================#

# Step 1: Generate certificate on management node
openssl genpkey -algorithm RSA -out web.key -pkeyopt rsa_keygen_bits:2048
openssl req -new -key web.key -out web.csr \
  -subj "/CN=web.example.com" \
  -addext "subjectAltName=DNS:web.example.com,DNS:web01.example.com,DNS:web02.example.com"

# Step 2: Get certificate from CA
# (submit web.csr to CA, receive web.crt)

# Step 3: Validate locally
./validate-certificate.sh web.crt web.key

# Step 4: Distribute securely
for host in web01 web02 web03; do
  scp web.crt root@$host:/etc/pki/tls/certs/
  scp web.key root@$host:/etc/pki/tls/private/
  ssh root@$host "chmod 644 /etc/pki/tls/certs/web-example-com.crt"
  ssh root@$host "chmod 600 /etc/pki/tls/private/web-example-com.key"
done

# Step 5: Reload services
for host in web01 web02 web03; do
  ssh root@$host "systemctl reload httpd"
done

# Step 6: Test each server
for host in web01 web02 web03; do
  echo "Testing $host..."
  curl -vk https://$host/ 2>&1 | grep "subject:"
done

21.8 Documentation Standards

Certificate Documentation Template

## Certificate: web.example.com

### Basic Information
- **Service:** Apache (httpd)
- **Server:** web01.example.com
- **Certificate Path:** `/etc/pki/tls/certs/web-example-com.crt`
- **Key Path:** `/etc/pki/tls/private/web-example-com.key`
- **Owner:** Web Team (webadmin@example.com)

### Certificate Details
- **Common Name (CN):** web.example.com
- **SANs:** web.example.com, www.example.com
- **Issuer:** Internal CA (ca.example.com)
- **Issue Date:** 2024-01-01
- **Expiry Date:** 2025-01-01
- **Key Type:** RSA 2048

### Renewal Process
- **Method:** certmonger automatic
- **Renewal Window:** 65 days before expiry
- **Post-Renewal:** `systemctl reload httpd`
- **Contact:** webadmin@example.com

### Service Configuration
- **Config File:** `/etc/httpd/conf.d/ssl.conf`
- **Service:** `httpd.service`
- **Restart Command:** `systemctl reload httpd`

### Troubleshooting
- **Logs:** `/var/log/httpd/ssl_error_log`
- **Test Command:** `curl -v https://web.example.com/`
- **Common Issues:** None reported

### Change History
- 2024-01-01: Initial deployment
- 2024-06-15: Added www.example.com SAN

21.9 Monitoring and Alerting

What to Monitor

✅ Certificate expiration (60, 30, 7 days before)
✅ Certificate validity (not expired, not yet valid)
✅ Certificate/key pair match
✅ Certificate trust chain
✅ Service health (is it using the cert?)
✅ certmonger tracking status
✅ Renewal success/failure

Simple Monitoring Script

#!/bin/bash
# monitor-certificates.sh
# Simple certificate monitoring

WARN_DAYS=30
CRIT_DAYS=7
EMAIL="admin@example.com"

check_cert() {
  local cert=$1
  local name=$(basename "$cert")

  # Check if expires within warning period
  if ! openssl x509 -in "$cert" -noout -checkend $((86400*WARN_DAYS)); then
    if ! openssl x509 -in "$cert" -noout -checkend $((86400*CRIT_DAYS)); then
      echo "🚨 CRITICAL: $name expires within $CRIT_DAYS days!"
      return 2
    else
      echo "⚠️ WARNING: $name expires within $WARN_DAYS days"
      return 1
    fi
  fi

  return 0
}

# Check all certificates
WARNINGS=0
CRITICALS=0

for cert in /etc/pki/tls/certs/*.crt; do
  [ -f "$cert" ] || continue
  check_cert "$cert"
  ret=$?
  [ $ret -eq 1 ] && ((WARNINGS++))
  [ $ret -eq 2 ] && ((CRITICALS++))
done

# Alert if issues found
if [ $CRITICALS -gt 0 ] || [ $WARNINGS -gt 0 ]; then
  echo "Certificate issues found: $CRITICALS critical, $WARNINGS warnings" | \
    mail -s "Certificate Alert: $(hostname)" "$EMAIL"
fi

21.10 Incident Response Procedures

Certificate Expiration Incident

#============================================#
# EXPIRED CERTIFICATE EMERGENCY
#============================================#

# Step 1: Assess impact
systemctl status httpd
journalctl -xe | grep -i cert

# Step 2: Quick fix - Get temporary cert
# Option A: Self-signed (for internal only!)
openssl req -x509 -nodes -days 30 -newkey rsa:2048 \
  -keyout /etc/pki/tls/private/temp-web01-example-com.key \
  -out /etc/pki/tls/certs/temp-web01-example-com.crt \
  -subj "/CN=$(hostname)"

# Option B: Restore from backup
cp /var/backups/certificates/latest/*.crt /etc/pki/tls/certs/
cp /var/backups/certificates/latest/*.key /etc/pki/tls/private/

# Step 3: Update service config to use temp cert
# Edit /etc/httpd/conf.d/ssl.conf
# SSLCertificateFile /etc/pki/tls/certs/temp-web01-example-com.crt
# SSLCertificateKeyFile /etc/pki/tls/private/temp-web01-example-com.key

# Step 4: Restart service
systemctl restart httpd

# Step 5: Get proper certificate ASAP
# Follow normal cert request process

# Step 6: Document incident
# What happened, why, how fixed, prevention

21.11 Best Practices Checklist

## Certificate Management Checklist

### File Organization
- [ ] Standard directory structure used
- [ ] Consistent naming convention
- [ ] Proper file permissions (600 for keys, 644 for certs)
- [ ] SELinux contexts correct

### Lifecycle Management
- [ ] Renewal process defined and documented
- [ ] Renewal reminders set (60, 30, 7 days)
- [ ] Automated renewal if possible (certmonger)
- [ ] Post-renewal actions defined

### Security
- [ ] Private keys protected (600 permissions)
- [ ] Keys never shared/emailed
- [ ] Strong key algorithm (RSA 2048+ or EC P-256)
- [ ] Strong signature (SHA-256+)

### Backup
- [ ] Certificates backed up
- [ ] Private keys backed up (encrypted)
- [ ] Backup tested and validated
- [ ] Restore procedure documented

### Documentation
- [ ] Certificate inventory maintained
- [ ] Each certificate documented
- [ ] Procedures written
- [ ] Contacts listed

### Monitoring
- [ ] Expiration monitoring enabled
- [ ] Alerts configured
- [ ] Health checks in place
- [ ] Incident response plan ready

### Validation
- [ ] Pre-deployment validation
- [ ] Post-deployment testing
- [ ] Regular audits scheduled

21.12 Key Takeaways

  1. Organization prevents confusion - Consistent structure and naming
  2. Permissions are critical - 600 for keys, 644 for certs
  3. Automate renewal - Use certmonger whenever possible
  4. Backup everything - But encrypt private keys
  5. Document thoroughly - Your future self will thank you
  6. Monitor proactively - Don’t wait for expiration
  7. Validate before deploy - Catch issues early
  8. Plan for incidents - Have recovery procedures ready

Quick Reference

┌─────────────────────────────────────────────────────────────┐
│ SERVICE CERTIFICATE BEST PRACTICES                          │
├─────────────────────────────────────────────────────────────┤
│ Files:     /etc/pki/tls/certs/*.crt (644)                   │
│            /etc/pki/tls/private/*.key (600)                 │
│ Naming:    [service]-[host]-[domain].[crt|key]              │
│ Renewal:   Automate with certmonger                         │
│ Backup:    Daily, encrypted, tested                         │
│ Monitor:   60, 30, 7 days before expiry                     │
│ Validate:  Before every deployment                          │
│ Document:  Everything, everyone                             │
└─────────────────────────────────────────────────────────────┘

Chapter Navigation

Chapter 22: certmonger Mastery

Set It and Forget It: certmonger is RHEL’s built-in certificate automation tool. Master it and you’ll never manually renew a certificate again.


22.1 What is certmonger?

certmonger is a certificate tracking and automatic renewal daemon for RHEL.

Think of it as:

  • 📋 Certificate tracker - Monitors expiration dates
  • 🔄 Auto-renewer - Renews before expiry
  • 🔗 CA integrator - Works with FreeIPA, local/internal CAs, and custom external CA helpers
  • ⚙️ Service integration - Runs commands after renewal

Why certmonger?

Without certmonger:

❌ Manual tracking of expiration dates
❌ Calendar reminders to renew
❌ Manual CSR generation
❌ Manual service restart after renewal
❌ Risk of missing renewals → outages

With certmonger:

✅ Automatic tracking
✅ Automatic renewal
✅ Automatic service reload
✅ Centralized monitoring
✅ No manual intervention!

22.2 Installation and Setup

Installation by RHEL Version

#============================================#
# INSTALL CERTMONGER
#============================================#

# RHEL 8/9/10
sudo dnf install certmonger -y

# RHEL 7
# sudo yum install certmonger -y

# Enable and start
sudo systemctl enable certmonger
sudo systemctl start certmonger

# Verify
systemctl status certmonger
sudo getcert list  # Should show empty list initially

22.3 Basic Usage

Requesting a Certificate

#============================================#
# BASIC CERTIFICATE REQUEST
#============================================#

# Self-signed (for testing)
sudo getcert request \
  -f /etc/pki/tls/certs/test.crt \
  -k /etc/pki/tls/private/test.key

# From FreeIPA / IdM
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/web.crt \
  -k /etc/pki/tls/private/web.key \
  -K HTTP/$(hostname -f)@REALM \
  -D $(hostname -f)

# For public Let's Encrypt certificates, use certbot (Chapter 24).
# certmonger is the native choice for IPA, local CA, and helper-based workflows.

Checking Status

#============================================#
# CHECK CERTIFICATE STATUS
#============================================#

# List all tracked certificates
sudo getcert list

# Check specific certificate by file
sudo getcert list -f /etc/pki/tls/certs/web.crt

# Check by request ID
sudo getcert list -i 20240101000000

# Verbose output
sudo getcert list -v

Status Values:

  • MONITORING: ✅ Certificate issued, tracking expiration
  • SUBMITTING: 🔄 Submitting request to CA
  • CA_UNREACHABLE: ❌ Can’t reach CA server
  • CA_REJECTED: ❌ CA rejected request
  • NEED_KEY_GEN_PIN: ⏸️ Waiting for PIN (HSM/token)
  • PRE_SAVE_COMMAND: 🔄 Running pre-save script
  • POST_SAVE_COMMAND: 🔄 Running post-save script

22.4 Advanced Options

Complete Request with All Options

#============================================#
# COMPLETE CERTMONGER REQUEST
#============================================#

sudo ipa-getcert request \
  -f /etc/pki/tls/certs/web.example.com.crt \              # Certificate file
  -k /etc/pki/tls/private/web.example.com.key \            # Private key file
  -K HTTP/web.example.com@EXAMPLE.COM \                    # Kerberos principal
  -D web.example.com \                                     # DNS name (SAN)
  -D www.example.com \                                     # Additional SAN
  -D api.example.com \                                     # Another SAN
  -U id-kp-serverAuth \                                    # Extended key usage
  -N CN=web.example.com,O=Example,C=US \                   # Subject DN
  -g 2048 \                                                # Key size
  -G rsa \                                                 # Key type
  -T caIPAserviceCert \                                    # IPA profile
  -C "systemctl reload httpd" \                            # Post-save command
  -B "systemctl stop httpd" \                              # Pre-save command
  -v \                                                     # Verbose
  -w                                                       # Wait for completion

# Check status
sudo getcert list -f /etc/pki/tls/certs/web.example.com.crt

Key Options Explained:

OptionPurposeExample
-fCertificate file path/etc/pki/tls/certs/web.crt
-kPrivate key file path/etc/pki/tls/private/web.key
-KKerberos principalHTTP/web.example.com@REALM
-DDNS SANweb.example.com
-NSubject DNCN=web,O=Example
-CPost-save commandsystemctl reload httpd
-BPre-save commandsystemctl stop httpd
-cCA nameIPA or external-ca
-TCertificate profilecaIPAserviceCert
-gKey size2048 or 4096
-GKey typersa or ec

22.5 Working with Different CAs

#============================================#
# CERTMONGER + FREEIPA
#============================================#

# Prerequisites: System enrolled to IPA
ipa-client-install

# Request certificate
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/internal.crt \
  -k /etc/pki/tls/private/internal.key \
  -K HTTP/$(hostname -f)@REALM \
  -D $(hostname -f) \
  -C "systemctl reload httpd"

# certmonger automatically:
# ✅ Submits request to IPA CA
# ✅ Obtains certificate
# ✅ Saves to file
# ✅ Runs reload command
# ✅ Tracks expiration
# ✅ Renews ~28 days before expiry

Public ACME Requires certbot

#============================================#
# PUBLIC ACME VS NATIVE CERTMONGER WORKFLOWS
#============================================#

# Public Let's Encrypt certificate:
# Use certbot, not a fake certmonger CA profile.
sudo certbot certonly --apache -d public.example.com

# Native FreeIPA / IdM certificate:
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/internal.crt \
  -k /etc/pki/tls/private/internal.key \
  -K HTTP/$(hostname -f)@REALM \
  -D $(hostname -f) \
  -C "systemctl reload httpd"

External CA (Manual Submission)

#============================================#
# CERTMONGER WITH EXTERNAL CA
#============================================#

# Configure external CA helper
sudo getcert add-ca -c external-ca \
  -e '/usr/local/bin/external-ca-submit.sh'

# Request certificate
sudo getcert request \
  -c external-ca \
  -f /etc/pki/tls/certs/external.crt \
  -k /etc/pki/tls/private/external.key

# Helper script must:
# 1. Read CSR from stdin
# 2. Submit to external CA
# 3. Return certificate on stdout

22.6 Managing Tracked Certificates

Modify Tracking

#============================================#
# MODIFY EXISTING CERTIFICATE TRACKING
#============================================#

# Update post-save command without rekeying
sudo getcert stop-tracking -f /etc/pki/tls/certs/web.crt
sudo getcert start-tracking \
  -f /etc/pki/tls/certs/web.crt \
  -k /etc/pki/tls/private/web.key \
  -C "systemctl reload httpd"
# Re-add -c, -K, -D, etc. if your original tracking entry used them

# Add additional SAN
sudo getcert resubmit -f /etc/pki/tls/certs/web.crt \
  -D additional.example.com

# Stop tracking (keep certificate)
sudo getcert stop-tracking -f /etc/pki/tls/certs/web.crt

# Remove completely
sudo getcert stop-tracking -f /etc/pki/tls/certs/web.crt -r

# Start tracking existing certificate
sudo getcert start-tracking \
  -f /etc/pki/tls/certs/existing.crt \
  -k /etc/pki/tls/private/existing.key

Force Renewal

#============================================#
# FORCE IMMEDIATE RENEWAL
#============================================#

# By file path
sudo ipa-getcert resubmit -f /etc/pki/tls/certs/web.crt

# By request ID
sudo getcert resubmit -i 20240101000000

# Wait for renewal
sudo getcert list -f /etc/pki/tls/certs/web.crt
# Wait for status: MONITORING

22.7 Renewal Timing

Understanding Renewal Windows

Certificate Lifecycle (365 days):

Day   0: Certificate issued
      │
      │ [Normal operation]
      │
Day 243: Renewal window starts (certmonger attempts renewal)
      │ (2/3 of cert lifetime: 365 × 2/3 ≈ 243 days)
      │
      │ [Renewal attempts every 8 hours if CA available]
      │
Day 335: Warning if not yet renewed (30 days left)
      │
Day 350: Critical if not yet renewed (15 days left)
      │
Day 365: Certificate expires → SERVICE OUTAGE if not renewed!

Default Behavior:

  • Renewal starts at 2/3 of certificate lifetime
  • 365-day cert → Renews at day 243 (122 days remaining)
  • 90-day cert → Renews at day 60 (30 days remaining)

22.8 Post-Save Commands

Reload vs Restart

#============================================#
# POST-SAVE COMMAND STRATEGIES
#============================================#

# PREFER: reload (no downtime)
-C "systemctl reload httpd"
-C "systemctl reload nginx"
-C "postfix reload"

# SOMETIMES NEEDED: restart
-C "systemctl restart slapd"  # OpenLDAP requires restart
-C "systemctl restart postgresql"  # PostgreSQL requires restart

# MULTIPLE COMMANDS: Use script
-C "/usr/local/bin/after-cert-renewal.sh"

# Script example:
#!/bin/bash
# /usr/local/bin/after-cert-renewal.sh
systemctl reload httpd
systemctl reload nginx
systemctl reload postfix
logger "Certificates renewed via certmonger"

22.9 Troubleshooting certmonger

Common Issues

Issue 1: CA_UNREACHABLE

# Symptom
sudo getcert list
# status: CA_UNREACHABLE

# Diagnosis
# For FreeIPA:
ipa ping  # Check IPA connectivity
klist  # Check Kerberos ticket

# Fix
kinit -k host/$(hostname -f)@REALM  # Renew host ticket
sudo ipa-getcert resubmit -f /etc/pki/tls/certs/web.crt

# Check IPA server
ssh ipa-server "sudo ipactl status"

Issue 2: CA_REJECTED

# Symptom
sudo getcert list
# status: CA_REJECTED
# ca-error: Server at https://ipa.example.com/ipa/xml unwilling to issue certificate

# Common causes:
# 1. Service principal doesn't exist
ipa service-show HTTP/$(hostname -f)
# If not found:
ipa service-add HTTP/$(hostname -f)

# 2. Host not enrolled
ipa host-show $(hostname -f)

# 3. Permission issue
# Check IPA permissions

# Retry
sudo ipa-getcert resubmit -f /etc/pki/tls/certs/web.crt

Issue 3: Renewal Not Happening

# Check certmonger is running
systemctl status certmonger

# Check certificate status
sudo getcert list -f /etc/pki/tls/certs/web.crt

# Check certmonger logs
sudo journalctl -u certmonger -f

# Force renewal
sudo ipa-getcert resubmit -f /etc/pki/tls/certs/web.crt

# Check renewal window
# Certificate renews at 2/3 of lifetime
# Check "expires" date in getcert list output

22.10 IdM ACME vs Public Let’s Encrypt

Keep the Workflows Separate

#============================================#
# CHOOSE THE RIGHT TOOL FOR THE RIGHT CA
#============================================#

# Public Let's Encrypt certificate:
# Use certbot (see Chapter 24).
sudo certbot certonly --apache -d public.example.com -d www.public.example.com

# Native FreeIPA / IdM certificate:
# Use certmonger + ipa-getcert.
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/internal.example.com.crt \
  -k /etc/pki/tls/private/internal.example.com.key \
  -K HTTP/internal.example.com@REALM \
  -D internal.example.com \
  -C "systemctl reload httpd"

# If IdM ACME is enabled, its ACME directory is your IPA server,
# not Let's Encrypt:
sudo certbot certonly \
  --server https://ipa.example.com/acme/directory \
  -d host.example.com

Important distinction:

  • Let’s Encrypt = public internet ACME CA
  • IdM/FreeIPA ACME = your internal IPA CA exposing an ACME endpoint
  • certmonger = native RHEL tracker/renewer for IPA and helper-based workflows

22.11 Monitoring certmonger

Status Monitoring

#============================================#
# MONITOR CERTMONGER
#============================================#

# Overview of all certificates
sudo getcert list

# Count certificates by status
sudo getcert list | grep "status:" | sort | uniq -c

# Find certificates expiring soon (30 days)
for cert in $(sudo getcert list | grep "certificate:" | sed -n "s/.*location='\\([^']*\\)'.*/\\1/p"); do
  if ! openssl x509 -in "$cert" -noout -checkend $((86400*30)) 2>/dev/null; then
    echo "⚠️ Expires soon: $cert"
  fi
done

# Check certmonger logs
sudo journalctl -u certmonger --since today

# Watch for renewal activity
sudo journalctl -u certmonger -f

# Check next renewal time
sudo getcert list | grep -A15 "Request ID" | grep "expires"

Health Check Script

#!/bin/bash
# certmonger-health-check.sh

echo "=== certmonger Health Check ==="

# certmonger running?
if systemctl is-active --quiet certmonger; then
  echo "✅ certmonger is running"
else
  echo "❌ certmonger is NOT running!"
  exit 1
fi

# Count tracked certificates
TOTAL=$(sudo getcert list | grep -c "Request ID")
echo "📋 Tracking $TOTAL certificates"

# Check status breakdown
echo ""
echo "Status breakdown:"
sudo getcert list | grep "status:" | sort | uniq -c

# Check for problems
PROBLEMS=$(sudo getcert list | grep "status:" | grep -v "MONITORING" | wc -l)
if [ $PROBLEMS -gt 0 ]; then
  echo ""
  echo "⚠️ $PROBLEMS certificates need attention:"
  sudo getcert list | grep -B5 "status:" | grep -E "(Request ID|status:)" | grep -v "MONITORING"
fi

# Check expiration warnings
echo ""
echo "Certificates expiring in 30 days:"
sudo getcert list | grep -A10 "Request ID" | grep "expires:" | \
  while read line; do
    # Parse and check expiry
    # (simplified - production script would parse dates properly)
    echo "$line"
  done

22.12 certmonger Configuration

Main Configuration File

#============================================#
# CERTMONGER CONFIGURATION
#============================================#

# Config location
/etc/certmonger/certmonger.conf

# Database location (tracked certificates)
/var/lib/certmonger/

# List configured CAs
sudo getcert list-cas

# Add custom CA
sudo getcert add-ca -c my-ca \
  -e '/usr/local/bin/my-ca-submit.sh'

# Remove CA
sudo getcert remove-ca -c my-ca

22.13 Best Practices

certmonger Best Practices

✅ **Always use post-save commands** (-C flag) to reload services
✅ **Track all production certificates** with certmonger
✅ **Monitor status weekly** with `getcert list`
✅ **Test renewal** before expiry with `resubmit`
✅ **Use verbose mode** (-v) when troubleshooting
✅ **Set up monitoring** for CA_UNREACHABLE status
✅ **Document request IDs** in your certificate inventory
✅ **Use IPA/certmonger for internal** certs, certbot for public Let's Encrypt
✅ **Keep certmonger logs** for audit trail
✅ **Test post-save commands** independently before use

What to Track with certmonger

# ✅ TRACK with certmonger:
- Web server certificates (Apache, NGINX)
- Mail server certificates (Postfix, Dovecot)
- LDAP server certificates (OpenLDAP)
- Application certificates (APIs, microservices)
- Service certificates (any TLS-enabled service)

# ❌ DON'T track with certmonger:
- CA root certificates (managed separately)
- Client certificates for users (different lifecycle)
- Test/temporary certificates
- Certificates you manage with other tools (certbot)

22.14 certmonger vs certbot

When to Use Which?

Featurecertmongercertbot
RHEL Native✅ Yes (included)❌ No (EPEL required)
FreeIPA Support✅ Native❌ No
Public Let’s Encrypt ACME❌ Use certbot instead✅ Yes (all versions)
Internal CA✅ Excellent❌ No
Apache/NGINX Config⏸️ Manual✅ Automatic
Service Integration✅ Post-save commands⏸️ Limited
Renewal Timing2/3 of lifetime30 days before
Red Hat Support✅ Yes❌ No (EPEL)

Recommendation:

  • Internal/Enterprise: Use certmonger + FreeIPA
  • Public/Simple: Use certbot (but know it requires EPEL)
  • Public certificates on any RHEL version: Use certbot for Let’s Encrypt

22.15 Advanced Scenarios

Scenario 1: High Availability Certificate Renewal

# Multiple servers with same service

# Server 1:
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/shared-service.crt \
  -k /etc/pki/tls/private/shared-service.key \
  -K HTTP/service.example.com@REALM \
  -D service.example.com \
  -C "systemctl reload httpd"

# Server 2: Same configuration
# Result: Each server manages its own cert independently
# Or: Use shared cert (copy files, not recommended)

Scenario 2: Wildcard Certificate

# Request wildcard from FreeIPA
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/wildcard.crt \
  -k /etc/pki/tls/private/wildcard.key \
  -K HTTP/*.example.com@REALM \
  -D *.example.com \
  -D example.com \
  -C "/usr/local/bin/reload-all-services.sh"

Scenario 3: EC Keys (Elliptic Curve)

# Request with EC key
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/ec-cert.crt \
  -k /etc/pki/tls/private/ec-cert.key \
  -K HTTP/$(hostname -f)@REALM \
  -G ec \
  -g nistp256  # or nistp384, nistp521

22.16 Key Takeaways

  1. certmonger is RHEL’s certificate automation
  2. Set it and forget it - Automatic renewal
  3. Works best with FreeIPA, internal CAs, and helper-based renewals
  4. Post-save commands reload services automatically
  5. Tracks expiration and renews at 2/3 of lifetime
  6. MONITORING status means all is well
  7. getcert list is your main monitoring tool

Quick Reference Card

┌──────────────────────────────────────────────────────────────┐
│ CERTMONGER MASTERY QUICK REFERENCE                           │
├──────────────────────────────────────────────────────────────┤
│ Install:      dnf install certmonger (yum on RHEL 7)         │
│ Start:        systemctl enable --now certmonger              │
│                                                              │
│ Request:      ipa-getcert request -f cert -k key -K principal│
│ List:         getcert list                                   │
│ Status:       getcert list -f /path/to/cert.crt              │
│ Resubmit:     ipa-getcert resubmit -f /path/to/cert.crt      │
│ Stop track:   getcert stop-tracking -f /path/to/cert.crt     │
│                                                              │
│ Status:       MONITORING = ✅ Good                           │
│               CA_UNREACHABLE = ❌ Check IPA/CA               │
│               CA_REJECTED = ❌ Check principal/permissions   │
│                                                              │
│ Logs:         journalctl -u certmonger -f                    │
│ Renewal:      Automatic at 2/3 of cert lifetime              │
│ Post-save:    -C "systemctl reload <service>"                │
└──────────────────────────────────────────────────────────────┘

✅ Native RHEL tool (Red Hat supported)
✅ Perfect for FreeIPA integration
✅ Best fit for FreeIPA and tracked renewal workflows

🧪 Hands-On Lab

Lab 11: certmonger Basics

Automate certificate renewal with certmonger

  • 📁 Location: labs/en_US/11-certmonger-basics/
  • ⏱️ Time: 30-35 minutes
  • 🎯 Level: Intermediate

Chapter Navigation

Chapter 23: Crypto-Policies Deep Dive

Revolutionary Feature: RHEL 8+ crypto-policies provide system-wide cryptographic configuration. Master this and you control security across all applications with one command.


23.1 The Problem crypto-policies Solves

Before Crypto-Policies (RHEL 7)

Configure security individually for EVERY application:

Apache:      /etc/httpd/conf.d/ssl.conf
             SSLProtocol, SSLCipherSuite

NGINX:       /etc/nginx/nginx.conf
             ssl_protocols, ssl_ciphers

Postfix:     /etc/postfix/main.cf
             smtpd_tls_protocols, smtpd_tls_mandatory_ciphers

OpenLDAP:    olcTLSProtocolMin, olcTLSCipherSuite

PostgreSQL:  ssl_min_protocol_version

OpenSSH:     /etc/ssh/sshd_config
             Ciphers, MACs, KexAlgorithms

... and 20+ more applications!

Result: Inconsistent security, configuration nightmare

After Crypto-Policies (RHEL 8/9/10)

✅ Set ONE system-wide policy
✅ All applications automatically comply
✅ Consistent security across the system
✅ Change policy in seconds, not hours

Game changer for enterprise management!


23.2 How Crypto-Policies Work

Architecture

┌──────────────────────────────────────────────────┐
│       update-crypto-policies --set DEFAULT       │
│              (Administrator command)             │
└───────────────────────┬──────────────────────────┘
                        │
                        ▼
┌──────────────────────────────────────────────────┐
│  /etc/crypto-policies/back-ends/                 │
│  (Generated config files for each library)       │
│  ├─ opensslcnf.config                            │
│  ├─ gnutls.config                                │
│  ├─ nss.config                                   │
│  ├─ bind.config                                  │
│  └─ ... more ...                                 │
└───────────────────────┬──────────────────────────┘
                        │
            ┌───────────┼───────────┐
            ▼           ▼           ▼
         OpenSSL     GnuTLS        NSS
            ↓           ↓           ↓
         Apache      NGINX       Firefox
         Postfix     vsftpd      Thunderbird
         OpenSSH     wget        Java apps

Key Insight: Applications read from back-end files, not directly from policy!


23.3 The Four Main Policies

Policy Comparison

PolicyTLS VersionsMin RSASHA-13DESMin DHUse Case
DEFAULT1.2, 1.320482048✅ Recommended
LEGACY1.0+1024⚠️⚠️1024Compatibility only
FUTURE1.2, 1.330723072High security
FIPS1.2, 1.320482048Federal compliance

DEFAULT Policy Details

# Balanced security and compatibility
Protocols:
  - TLS 1.2
  - TLS 1.3

Minimum Key Sizes:
  - RSA: 2048 bits
  - DH: 2048 bits
  - ECC: secp256r1 (P-256)

Allowed Ciphers:
  - AES-128-GCM
  - AES-256-GCM
  - ChaCha20-Poly1305
  - AES-128-CBC
  - AES-256-CBC

Signature Algorithms:
  - SHA-256
  - SHA-384
  - SHA-512

Blocked:
  - TLS 1.0, 1.1
  - MD5
  - SHA-1 signatures
  - 3DES, RC4, DES
  - RSA < 2048 bits
  - Export ciphers

23.4 Viewing and Changing Policies

Basic Commands

#============================================#
# CRYPTO-POLICY BASIC OPERATIONS
#============================================#

# View current policy
update-crypto-policies --show
# Output: DEFAULT

# List available policies
ls /usr/share/crypto-policies/policies/
# DEFAULT.pol  FUTURE.pol  LEGACY.pol  FIPS.pol

# Set policy
sudo update-crypto-policies --set FUTURE

# You MUST restart services for changes to take effect!
sudo systemctl restart httpd nginx postfix slapd

# Or reboot (ensures everything picks up changes)
sudo reboot

What Happens When You Change Policy

#============================================#
# BEHIND THE SCENES
#============================================#

# Before
update-crypto-policies --show
# DEFAULT

# Change
sudo update-crypto-policies --set FUTURE

# Updates generated:
ls -l /etc/crypto-policies/back-ends/
# -rw-r--r--. opensslcnf.config    ← Updated!
# -rw-r--r--. gnutls.config        ← Updated!
# -rw-r--r--. nss.config           ← Updated!
# -rw-r--r--. bind.config          ← Updated!
# ... all back-ends updated ...

# View OpenSSL config
cat /etc/crypto-policies/back-ends/opensslcnf.config
# Shows actual OpenSSL configuration applied

23.5 Subpolicies (RHEL 9+)

Policy Modifiers

RHEL 9 introduced subpolicies - fine-tune existing policies!

#============================================#
# CRYPTO-POLICY SUBPOLICIES (RHEL 9+)
#============================================#

# Base policy with modifier
sudo update-crypto-policies --set DEFAULT:NO-SHA1

# Multiple modifiers
sudo update-crypto-policies --set DEFAULT:NO-SHA1:GOST

# Available subpolicy modules
ls /usr/share/crypto-policies/policies/modules/
# AD-SUPPORT.pmod
# GOST.pmod
# NO-CAMELLIA.pmod
# NO-SHA1.pmod
# NO-ENFORCE-EMS.pmod
# ...and more

# View module details
cat /usr/share/crypto-policies/policies/modules/NO-SHA1.pmod

Common Subpolicies:

SubpolicyEffectUse Case
NO-SHA1Completely disable SHA-1Extra security
AD-SUPPORTEnable AD compatibilityMixed Windows/Linux
GOSTEnable GOST algorithmsRussian requirements
NO-CAMELLIADisable Camellia cipherSpecific compliance
NO-ENFORCE-EMSDisable Extended Master SecretCompatibility

23.6 Creating Custom Policy Modules

Custom Module Example

#============================================#
# CREATE CUSTOM POLICY MODULE
#============================================#

# Create custom module
sudo vi /etc/crypto-policies/policies/modules/CUSTOM-SECURITY.pmod

# Example content:
min_rsa_size = 4096
min_dh_size = 3072
min_dsa_size = 3072
sha1_in_certs = 0
arbitrary_dh_groups = 0
ssh_certs = 0

# Apply
sudo update-crypto-policies --set DEFAULT:CUSTOM-SECURITY

# Restart services
sudo systemctl restart httpd nginx postfix

# Verify
openssl ciphers -v | grep -E "RSA|DH"

23.7 Per-Application Overrides

When to Override

Sometimes ONE application needs different settings than system policy:

Example: Legacy application needs TLS 1.1, but system uses DEFAULT

Apache Override

#============================================#
# APACHE CRYPTO-POLICY OVERRIDE
#============================================#

# /etc/httpd/conf.d/ssl.conf

# Option 1: Include crypto-policy, then override
Include /etc/crypto-policies/back-ends/httpd.config

# Then add overrides:
SSLProtocol all -SSLv3  # Re-enable TLS 1.0/1.1

# Option 2: Completely opt-out
# Don't include crypto-policy file
# Manually configure everything:
SSLProtocol TLSv1.1 TLSv1.2 TLSv1.3
SSLCipherSuite HIGH:!aNULL:!MD5

# ⚠️ Warning: You now manage Apache TLS manually
# System crypto-policy changes won't affect Apache

Better Approach: Create custom policy module instead of per-app overrides!


23.8 Testing Policy Impact

Before Changing Policy

#============================================#
# TEST POLICY CHANGE IMPACT
#============================================#

# 1. Document current state
update-crypto-policies --show > /tmp/current-policy.txt
systemctl list-units --type=service --state=running > /tmp/running-services.txt

# 2. Test applications
curl https://localhost/
psql -h localhost  # etc.

# 3. Change policy on test system first
sudo update-crypto-policies --set FUTURE

# 4. Restart services
sudo systemctl restart httpd nginx postfix

# 5. Test thoroughly
./test-all-services.sh

# 6. If problems: Revert
sudo update-crypto-policies --set DEFAULT

# 7. If successful: Document and deploy to production

23.9 Policy Impact on Certificates

What Policies Control

Crypto-policies affect:

  • ✅ TLS protocol versions allowed
  • ✅ Cipher suites available
  • ✅ Minimum key sizes accepted
  • ✅ Signature algorithms allowed
  • ✅ Diffie-Hellman parameters
  • ✅ Certificate validation strictness

Crypto-policies DON’T affect:

  • ❌ Which certificates to use (still configured per service)
  • ❌ Certificate file locations
  • ❌ CA trust store (that’s update-ca-trust)
  • ❌ Certificate issuance

Certificate Compatibility Matrix

Certificate TypeDEFAULTLEGACYFUTUREFIPS
RSA 1024 bit⚠️
RSA 2048 bit
RSA 3072 bit
RSA 4096 bit
EC P-256
EC P-384
SHA-1 signature⚠️
SHA-256 signature

23.10 Troubleshooting Crypto-Policies

Common Issues

Issue 1: Application Fails After Policy Change

# Symptom
sudo update-crypto-policies --set FUTURE
sudo systemctl restart httpd
# httpd fails to start

# Diagnosis
sudo journalctl -xe -u httpd | grep -i cipher

# Common cause: Application has hard-coded weak ciphers

# Solution 1: Revert policy
sudo update-crypto-policies --set DEFAULT

# Solution 2: Update application config
# Remove hard-coded cipher specifications

# Solution 3: Create custom policy module

Issue 2: “No Shared Cipher”

# Symptom: Clients can't connect

# Test
openssl s_client -connect server:443

# If shows "no shared cipher":

# Check policy
update-crypto-policies --show

# Test client capabilities
openssl s_client -connect server:443 -cipher 'ALL' -tls1_2

# Temporary fix (not recommended long-term):
sudo update-crypto-policies --set LEGACY

# Proper fix: Update client to support TLS 1.2+ and modern ciphers

Issue 3: Policy Doesn’t Seem to Apply

# Check if application is overriding policy

# Apache
grep -r "SSLProtocol\|SSLCipherSuite" /etc/httpd/
# If found: App is overriding policy

# NGINX
grep -r "ssl_protocols\|ssl_ciphers" /etc/nginx/
# If found: App is overriding policy

# Solution: Remove overrides, let crypto-policy handle it
# Or: Document why override is necessary

23.11 Best Practices

Recommendations

✅ **Use DEFAULT policy** for most environments
✅ **Test before deploying** new policies
✅ **Document policy choices** and reasons
✅ **Restart services** after policy changes
✅ **Avoid per-app overrides** when possible
✅ **Use subpolicies** (RHEL 9+) for fine-tuning
✅ **Monitor for compatibility** issues
✅ **Keep LEGACY temporary** if used
✅ **Plan migrations** when changing policies
✅ **Update clients** rather than weaken policy

When to Use Each Policy

DEFAULT:

  • ✅ Most production environments
  • ✅ Balanced security/compatibility
  • ✅ Recommended starting point
  • ✅ Tested and maintained by Red Hat

LEGACY:

  • ⚠️ Temporary during migrations only!
  • ⚠️ Supporting very old clients
  • ⚠️ Testing compatibility issues
  • ❌ Never long-term!

FUTURE:

  • ✅ High-security environments
  • ✅ All clients are modern
  • ✅ Want strongest settings
  • ✅ Planning for future standards

FIPS:

  • ✅ Federal compliance required
  • ✅ Government contracts
  • ✅ Regulated industries
  • ✅ Certification requirements

23.12 FIPS Policy Deep Dive

Enabling FIPS Mode

#============================================#
# ENABLE FIPS MODE
#============================================#

# Check current status
fips-mode-setup --check
# FIPS mode is disabled.

# Enable FIPS mode
sudo fips-mode-setup --enable

# MUST reboot
sudo reboot

# Verify after reboot
fips-mode-setup --check
# FIPS mode is enabled.

# Crypto-policy automatically set to FIPS
update-crypto-policies --show
# FIPS

FIPS Mode Requirements:

  • Must be enabled at install OR with fips-mode-setup
  • Requires reboot
  • Affects entire system
  • Only FIPS-approved algorithms available
  • Performance impact (~10-20% slower)

FIPS Policy Specifics

# What FIPS policy allows:
✅ TLS 1.2, 1.3
✅ RSA 2048+ bits
✅ AES-128, AES-256 (GCM mode)
✅ SHA-256, SHA-384, SHA-512
✅ ECDHE key exchange

# What FIPS blocks:
❌ TLS 1.0, 1.1
❌ RSA < 2048 bits
❌ 3DES, RC4, DES
❌ MD5, SHA-1
❌ Non-approved algorithms
❌ CBC mode ciphers (in some cases)

23.13 Monitoring and Auditing

Check Policy Compliance

#============================================#
# VERIFY CRYPTO-POLICY COMPLIANCE
#============================================#

# Current policy
update-crypto-policies --show

# Which applications use crypto-policies?
ls -l /etc/crypto-policies/back-ends/

# Verify OpenSSL follows policy
openssl ciphers -v | head -20

# Check specific application config
# Apache
cat /etc/crypto-policies/back-ends/httpd.config

# Test actual connection
openssl s_client -connect localhost:443 -tls1_3

# Verify no overrides
grep -r "SSLProtocol\|SSLCipherSuite" /etc/httpd/ | grep -v crypto-policies
# Should be empty or commented out

23.14 Troubleshooting Workflow

Systematic Approach

Application fails after policy change?
    │
    ├─ Step 1: Identify error
    │   └─ Check logs: journalctl -xe
    │
    ├─ Step 2: Verify policy active
    │   └─ update-crypto-policies --show
    │
    ├─ Step 3: Test with LEGACY
    │   └─ sudo update-crypto-policies --set LEGACY
    │   └─ If works → cipher/protocol issue
    │
    ├─ Step 4: Identify incompatibility
    │   └─ openssl s_client -cipher 'ALL' -tls1
    │   └─ Find what client/server needs
    │
    ├─ Step 5: Choose solution
    │   ├─ A) Update client (best)
    │   ├─ B) Create custom module (good)
    │   └─ C) Per-app override (last resort)
    │
    └─ Step 6: Document and deploy
        └─ Why override needed, plan to remove

23.15 Key Takeaways

  1. Crypto-policies are RHEL 8+ only (not in RHEL 7)
  2. DEFAULT policy is recommended for most cases
  3. Changes require service restarts to take effect
  4. Affects ALL crypto applications system-wide
  5. Subpolicies provide fine-tuning (RHEL 9+)
  6. Avoid per-app overrides when possible
  7. Test before deploying new policies
  8. LEGACY is temporary only!

Quick Reference Card

┌──────────────────────────────────────────────────────────────┐
│ CRYPTO-POLICIES QUICK REFERENCE                              │
├──────────────────────────────────────────────────────────────┤
│ Available:    RHEL 8, 9, 10 only (not RHEL 7)                │
│                                                              │
│ View:         update-crypto-policies --show                  │
│ Set:          sudo update-crypto-policies --set <POLICY>     │
│ Policies:     DEFAULT, LEGACY, FUTURE, FIPS                  │
│                                                              │
│ Subpolicy:    update-crypto-policies --set DEFAULT:NO-SHA1   │
│               (RHEL 9+ only)                                 │
│                                                              │
│ Back-ends:    /etc/crypto-policies/back-ends/                │
│ Modules:      /usr/share/crypto-policies/policies/modules/   │
│                                                              │
│ After change: systemctl restart <services>                   │
│               OR reboot                                      │
│                                                              │
│ DEFAULT:      TLS 1.2+, RSA 2048+, No SHA-1                  │
│ LEGACY:       TLS 1.0+, allows weak (temporary only!)        │
│ FUTURE:       TLS 1.2+, RSA 3072+, strictest                 │
│ FIPS:         Federal compliance (requires FIPS mode)        │
└──────────────────────────────────────────────────────────────┘

⚠️ RHEL 7 doesn't have crypto-policies (manual config only)
✅ DEFAULT works for 95% of environments

🧪 Hands-On Lab

Lab 12: Crypto-Policies

Understand and configure system-wide crypto-policies

  • 📁 Location: labs/en_US/12-crypto-policies/
  • ⏱️ Time: 25-30 minutes
  • 🎯 Level: Intermediate

Chapter Navigation

Chapter 24: Let’s Encrypt & certbot

Free Public Certificates: Let’s Encrypt provides free, automated certificates for public-facing websites. Learn how to use it on RHEL with certbot.


24.1 What is Let’s Encrypt?

Let’s Encrypt is a free, automated, and open Certificate Authority.

Key Features:

  • Free certificates (no cost)
  • Automated issuance (via ACME protocol)
  • Auto-renewal (every 60-90 days)
  • Widely trusted (in all major browsers)
  • Domain validation (DV certificates)

Limitations:

  • Public domains only (must be internet-accessible for validation)
  • 90-day validity (short-lived, requires automation)
  • Domain Validation only (no Organization or Extended Validation)
  • No wildcard with HTTP-01 (requires DNS-01 challenge)

24.2 Let’s Encrypt on RHEL: Public ACME and Native Alternatives

Method 1: certbot (Traditional)

⚠️ CRITICAL: EPEL Required

certbot is NOT available in official RHEL repositories.

It requires EPEL (Extra Packages for Enterprise Linux), a community-maintained repository that is NOT officially supported by Red Hat.

For Enterprise Production Environments:

  • Consider FreeIPA with certmonger (Chapter 19)
  • Or commercial CA with certmonger (Chapter 22)
  • Or manual certificate management

EPEL is suitable for:

  • Development/testing environments
  • Small deployments where EPEL risk is acceptable
  • Situations where free certificates outweigh support concerns

certbot Tool:

  • Full automation
  • Apache/NGINX plugins
  • Automatic configuration
  • Renewal timers
  • ⚠️ Requires EPEL

Method 2: certmonger for Internal/Private CAs

Native RHEL Solution:

  • ✅ No EPEL needed
  • ✅ Red Hat supported
  • ✅ Best fit for FreeIPA / IdM and local CA workflows
  • ⏸️ Manual web server config (no Apache/NGINX plugins)
  • ❌ Not a direct replacement for certbot with public Let’s Encrypt

We’ll use certbot for public Let’s Encrypt and certmonger for native internal workflows.


24.3 certbot Installation

RHEL 7

#============================================#
# INSTALL CERTBOT ON RHEL 7 (REQUIRES EPEL!)
#============================================#

# ⚠️ WARNING: Enabling third-party repository

# Step 1: Enable EPEL
sudo yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -y

# Verify EPEL enabled
yum repolist | grep epel

# Step 2: Install certbot
sudo yum install certbot python2-certbot-apache python2-certbot-nginx -y

# Verify
certbot --version

RHEL 8

#============================================#
# INSTALL CERTBOT ON RHEL 8 (REQUIRES EPEL!)
#============================================#

# ⚠️ WARNING: Enabling third-party repository

# Step 1: Enable EPEL
sudo dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm -y

# Or if you have subscription:
sudo dnf install epel-release -y

# Step 2: Install certbot
sudo dnf install certbot python3-certbot-apache python3-certbot-nginx -y

# Verify
certbot --version

RHEL 9/10

#============================================#
# INSTALL CERTBOT ON RHEL 9/10 (REQUIRES EPEL!)
#============================================#

# ⚠️ WARNING: Enabling third-party repository

# Step 1: Enable EPEL
sudo dnf install epel-release -y

# Step 2: Install certbot
sudo dnf install certbot python3-certbot-apache python3-certbot-nginx -y

# Verify
certbot --version

Remember: EPEL is community-supported. For enterprise production, consider FreeIPA + certmonger (native RHEL solution).


24.4 certbot Usage - Apache

Automatic Apache Configuration

#============================================#
# CERTBOT WITH APACHE (AUTOMATED!)
#============================================#

# Prerequisites:
# - Apache installed and running
# - Port 80 accessible from internet
# - Domain resolves to this server
# - EPEL repository enabled

# Obtain certificate and auto-configure Apache
sudo certbot --apache -d www.example.com -d example.com

# certbot will:
# 1. Generate certificate from Let's Encrypt
# 2. Automatically configure Apache SSL
# 3. Set up HTTP→HTTPS redirect
# 4. Configure auto-renewal

# Interactive prompts:
# - Email address (for renewal notices)
# - Agree to ToS
# - Redirect HTTP to HTTPS? (choose yes)

# Non-interactive (automation):
sudo certbot --apache \
  -d www.example.com \
  -d example.com \
  --non-interactive \
  --agree-tos \
  --email admin@example.com \
  --redirect

# Certificate location:
# /etc/letsencrypt/live/www.example.com/fullchain.pem
# /etc/letsencrypt/live/www.example.com/privkey.pem

24.5 certbot Usage - NGINX

Automatic NGINX Configuration

#============================================#
# CERTBOT WITH NGINX (AUTOMATED!)
#============================================#

# Prerequisites:
# - NGINX installed and running
# - Port 80 accessible
# - Domain resolves to server

# Obtain and configure
sudo certbot --nginx -d api.example.com

# Non-interactive
sudo certbot --nginx \
  -d api.example.com \
  --non-interactive \
  --agree-tos \
  --email admin@example.com \
  --redirect

# certbot updates NGINX config automatically!
# No manual SSL configuration needed

24.6 certbot Manual Mode (Standalone)

Without Web Server Plugin

#============================================#
# CERTBOT STANDALONE (NO PLUGIN)
#============================================#

# Use when:
# - Web server not Apache/NGINX
# - Want manual control over config
# - Using custom web server

# Obtain certificate only (doesn't configure server)
sudo certbot certonly --standalone \
  -d app.example.com \
  --non-interactive \
  --agree-tos \
  --email admin@example.com

# Certificate saved to:
# /etc/letsencrypt/live/app.example.com/fullchain.pem
# /etc/letsencrypt/live/app.example.com/privkey.pem

# Manually configure your service to use it
# Apache example:
# SSLCertificateFile /etc/letsencrypt/live/app.example.com/fullchain.pem
# SSLCertificateKeyFile /etc/letsencrypt/live/app.example.com/privkey.pem

24.7 Renewal

Automatic Renewal

#============================================#
# CERTBOT AUTO-RENEWAL
#============================================#

# certbot automatically sets up renewal timer
systemctl list-timers | grep certbot
# Should show: certbot-renew.timer

# View timer details
systemctl status certbot-renew.timer

# Test renewal (dry run - doesn't actually renew)
sudo certbot renew --dry-run

# Force actual renewal (if needed)
sudo certbot renew --force-renewal

# Check certificate expiration
sudo certbot certificates

# Renewal runs twice daily
# Renews certificates expiring within 30 days

Renewal Hooks

#============================================#
# RENEWAL HOOKS (DEPLOY COMMANDS)
#============================================#

# Add hook to reload service after renewal
sudo certbot renew --deploy-hook "systemctl reload nginx"

# Or create hook script
sudo vi /etc/letsencrypt/renewal-hooks/deploy/reload-services.sh

#!/bin/bash
systemctl reload httpd
systemctl reload nginx
systemctl reload postfix

sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-services.sh

# Hooks in /etc/letsencrypt/renewal-hooks/:
# - pre/: Run before renewal
# - post/: Run after renewal (even if failed)
# - deploy/: Run after successful renewal only

24.8 Wildcard Certificates

DNS-01 Challenge Required

#============================================#
# WILDCARD CERTIFICATE (DNS CHALLENGE)
#============================================#

# Wildcard requires DNS-01 challenge
# (can't use HTTP-01 for *.example.com)

# Manual DNS challenge
sudo certbot certonly --manual \
  --preferred-challenges dns \
  -d "*.example.com" \
  -d "example.com"

# certbot will prompt you to:
# 1. Create TXT record in DNS
# 2. Wait for propagation
# 3. Press Enter to continue

# DNS automation with plugins (if available)
# sudo certbot certonly --dns-route53 -d "*.example.com"
# (requires DNS provider plugin)

24.9 Where certmonger Fits

Use certmonger for Internal or Private CA Workflows

#============================================#
# CERTMONGER FOR IPA / INTERNAL CA WORKFLOWS
#============================================#

# Install certmonger
# RHEL 8/9/10
sudo dnf install certmonger -y

# RHEL 7
# sudo yum install certmonger -y

sudo systemctl enable --now certmonger

# Request an internal certificate from FreeIPA / IdM
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/internal.example.com.crt \
  -k /etc/pki/tls/private/internal.example.com.key \
  -K HTTP/internal.example.com@REALM \
  -D internal.example.com \
  -C "systemctl reload httpd"

# Check status
sudo getcert list

Keep these workflows separate:

Use CaseRecommended Tool
Public internet certificate from Let’s Encryptcertbot
Internal service certificate from FreeIPA / IdMcertmonger with ipa-getcert
ACME against your own IdM ACME endpointcertbot or another ACME client

Important: IdM ACME, when enabled, is your own FreeIPA / IdM CA exposing an ACME endpoint. It is not Let’s Encrypt.


24.10 Troubleshooting certbot

Common Issues

Issue 1: Challenge Validation Failed

# Symptom
sudo certbot --apache -d example.com
# Error: Challenge validation failed

# Common causes:
# 1. Port 80 not accessible
curl http://example.com/.well-known/acme-challenge/test
# Should be accessible from internet

# 2. Firewall blocking
sudo firewall-cmd --list-services | grep http

# 3. DNS not resolving
nslookup example.com

# 4. Another service on port 80
ss -tlnp | grep :80

Issue 2: Renewal Failed

# Check renewal logs
sudo cat /var/log/letsencrypt/letsencrypt.log

# Common causes:
# - Port 80 blocked
# - DNS changed
# - Rate limit hit

# Test renewal manually
sudo certbot renew --dry-run

Issue 3: Permission Errors

# Fix certbot permissions
sudo chmod 0755 /etc/letsencrypt/{live,archive}
sudo chmod 0644 /etc/letsencrypt/live/*/fullchain.pem
sudo chmod 0600 /etc/letsencrypt/live/*/privkey.pem

24.11 Best Practices

certbot Best Practices

✅ **Use certbot for public-facing sites only**
✅ **Ensure port 80 accessible** (HTTP-01 challenge)
✅ **Test renewal regularly** (certbot renew --dry-run)
✅ **Monitor renewal timer** (systemctl status certbot-renew.timer)
✅ **Set up email notifications** for failures
✅ **Use renewal hooks** to reload services
✅ **Backup /etc/letsencrypt/** directory
✅ **Document EPEL dependency** in runbooks
✅ **Have fallback plan** if EPEL unavailable
✅ **For internal services, use certmonger + FreeIPA/local CA**

When to Use certbot

✅ Good Use Cases:

  • Public-facing websites
  • Development/staging environments
  • Small deployments
  • Cost-sensitive projects
  • Quick HTTPS setup

❌ Consider Alternatives:

  • Enterprise production (use FreeIPA)
  • Internal-only services (use FreeIPA)
  • Strict vendor support required (no EPEL)
  • Air-gapped environments (no internet)
  • Compliance requiring commercial CA

24.12 Alternative: FreeIPA for Internal

Comparison

For INTERNAL services:

# Instead of Let's Encrypt (public CA)
# Use FreeIPA (internal CA)

# FreeIPA advantages for internal:
✅ No internet dependency
✅ Red Hat supported
✅ Works offline
✅ No EPEL needed
✅ Integrated with RHEL
✅ Certificate profiles
✅ Centralized management

# Setup:
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/internal.crt \
  -k /etc/pki/tls/private/internal.key \
  -K HTTP/$(hostname -f)@REALM \
  -C "systemctl reload httpd"

# See Chapter 19 for FreeIPA details

24.13 Migration from certbot to certmonger

When Moving Internal Services to FreeIPA / IdM

If you are replacing public Let’s Encrypt certificates with internal PKI for non-public services, move the service to FreeIPA / IdM rather than trying to make certmonger talk directly to Let’s Encrypt.

#============================================#
# MIGRATE CERTBOT → CERTMONGER FOR INTERNAL PKI
#============================================#

# Step 1: Inventory existing certbot-managed certificates
sudo certbot certificates

# Step 2: Request the replacement internal certificate from IPA
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/internal.example.com.crt \
  -k /etc/pki/tls/private/internal.example.com.key \
  -K HTTP/internal.example.com@REALM \
  -D internal.example.com \
  -C "systemctl reload httpd"

# Step 3: Update Apache/NGINX config
# Change from /etc/letsencrypt/live/... to /etc/pki/tls/...

# Step 4: Reload and verify
sudo systemctl reload httpd
sudo getcert list

# Step 5: Disable certbot renewals only after all public certs are gone
sudo systemctl disable --now certbot-renew.timer

24.14 Rate Limits

Let’s Encrypt Limits

Be aware of rate limits:

Limit TypeValuePeriod
Certificates per domain50per week
Duplicate certificates5per week
Failed validations5per hour
New accounts10per IP per 3 hours

Avoid hitting limits:

  • ✅ Use –dry-run for testing
  • ✅ Use staging environment first
  • ✅ Don’t request same cert repeatedly
  • ✅ Plan deployments carefully

Staging Environment:

# Test against staging (doesn't count against limits)
sudo certbot --apache \
  -d test.example.com \
  --test-cert  # Uses staging environment

# When ready, get production cert:
sudo certbot --apache -d test.example.com

24.15 Complete Examples

Example 1: Apache with certbot

#!/bin/bash
# setup-apache-letsencrypt.sh

DOMAIN="www.example.com"
EMAIL="admin@example.com"

echo "=== Apache + Let's Encrypt Setup ==="
echo "⚠️ Requires EPEL repository"

# 1. Install Apache
sudo dnf install -y httpd

# 2. Enable EPEL
sudo dnf install -y epel-release

# 3. Install certbot
sudo dnf install -y certbot python3-certbot-apache

# 4. Ensure port 80 open
sudo firewall-cmd --add-service=http --permanent
sudo firewall-cmd --add-service=https --permanent
sudo firewall-cmd --reload

# 5. Start Apache
sudo systemctl enable --now httpd

# 6. Obtain certificate
sudo certbot --apache \
  -d "$DOMAIN" \
  --non-interactive \
  --agree-tos \
  --email "$EMAIL" \
  --redirect

# 7. Verify
sudo certbot certificates

# 8. Test
curl -I https://$DOMAIN/

echo "✅ Apache + Let's Encrypt configured!"
echo "⚠️ Remember: certbot requires EPEL (community-supported)"

Example 2: NGINX with certbot

#!/bin/bash
# setup-nginx-letsencrypt.sh

DOMAIN="api.example.com"
EMAIL="admin@example.com"

echo "=== NGINX + Let's Encrypt Setup ==="

# 1. Install NGINX
sudo dnf install -y nginx

# 2. Install certbot
sudo dnf install -y epel-release
sudo dnf install -y certbot python3-certbot-nginx

# 3. Create basic NGINX config
sudo tee /etc/nginx/conf.d/$DOMAIN.conf << EOF
server {
    listen 80;
    server_name $DOMAIN;
    root /usr/share/nginx/html;
}
EOF

# 4. Start NGINX
sudo systemctl enable --now nginx

# 5. Obtain certificate
sudo certbot --nginx \
  -d "$DOMAIN" \
  --non-interactive \
  --agree-tos \
  --email "$EMAIL"

# 6. certbot automatically updates NGINX config!

# 7. Verify
curl -I https://$DOMAIN/

echo "✅ NGINX + Let's Encrypt configured!"

24.16 Backup and Restore

Backup Let’s Encrypt Certificates

#============================================#
# BACKUP CERTBOT/LETSENCRYPT
#============================================#

# Backup entire letsencrypt directory
sudo tar czf letsencrypt-backup-$(date +%Y%m%d).tar.gz \
  /etc/letsencrypt/

# Store backup securely (contains private keys!)

# Restore
sudo tar xzf letsencrypt-backup-YYYYMMDD.tar.gz -C /

# Verify
sudo certbot certificates

24.17 Key Takeaways

  1. Let’s Encrypt provides free certificates for public domains
  2. certbot requires EPEL on ALL RHEL versions (not officially supported)
  3. certbot automates Apache/NGINX configuration
  4. 90-day validity requires automatic renewal
  5. certmonger remains the native choice for FreeIPA/internal CA workflows
  6. For internal services: Use FreeIPA instead
  7. Test with –dry-run to avoid rate limits
  8. Monitor renewal timer - check it’s running

Quick Reference Card

┌──────────────────────────────────────────────────────────────┐
│ LET'S ENCRYPT & CERTBOT QUICK REFERENCE                      │
├──────────────────────────────────────────────────────────────┤
│ ⚠️ REQUIRES EPEL (community repository, not Red Hat!)        │
│                                                              │
│ Install:      dnf install epel-release                       │
│               dnf install certbot python3-certbot-apache     │
│                                                              │
│ Apache:       certbot --apache -d example.com                │
│ NGINX:        certbot --nginx -d example.com                 │
│ Standalone:   certbot certonly --standalone -d example.com   │
│                                                              │
│ Test:         certbot renew --dry-run                        │
│ Renew:        certbot renew (automatic via timer)            │
│ List:         certbot certificates                           │
│                                                              │
│ Certs:        /etc/letsencrypt/live/<domain>/                │
│               fullchain.pem, privkey.pem                     │
│                                                              │
│ Timer:        systemctl status certbot-renew.timer           │
│ Logs:         /var/log/letsencrypt/letsencrypt.log           │
│                                                              │
│ Alternative:  certmonger + FreeIPA / internal CA              │
│               ipa-getcert request ...                        │
└──────────────────────────────────────────────────────────────┘

⚠️ certbot NOT available in official RHEL repos
⚠️ EPEL is community-supported, not Red Hat supported
✅ For enterprise: Consider FreeIPA + certmonger (Chapter 19)
✅ Use certmonger for FreeIPA / internal CA renewals

🧪 Hands-On Lab

Lab 13: Let’s Encrypt & Certbot

Obtain and auto-renew Let’s Encrypt certificates

  • 📁 Location: labs/en_US/13-letsencrypt-certbot/
  • ⏱️ Time: 30-40 minutes
  • 🎯 Level: Intermediate

Chapter Navigation

Chapter 25: Ansible Automation for Certificates

Scale Up: Manage certificates across hundreds of RHEL systems with Ansible. Automate deployment, renewal, and monitoring at enterprise scale.


25.1 Why Ansible for Certificates?

Manual Management:

❌ SSH to 100 servers individually
❌ Copy certificates one by one
❌ Configure services manually
❌ Track renewals per server
❌ Hope you didn't miss any

Ansible Automation:

✅ Deploy certificates to 100 servers in minutes
✅ Consistent configuration
✅ Idempotent (safe to run repeatedly)
✅ Version controlled (Git)
✅ Auditable (who changed what when)
✅ Rollback capable

25.2 Prerequisites

Install Ansible on RHEL

#============================================#
# INSTALL ANSIBLE
#============================================#

# RHEL 8/9/10
sudo dnf install ansible-core -y

# Or from Ansible repository for latest
sudo dnf install epel-release  # If using EPEL
sudo dnf install ansible -y

# Verify
ansible --version

# Install community.crypto collection (ESSENTIAL!)
ansible-galaxy collection install community.crypto

25.3 Ansible Inventory for Certificate Management

Example Inventory

#============================================#
# inventory/hosts.ini
#============================================#

[webservers]
web01.example.com
web02.example.com
web03.example.com

[mailservers]
mail01.example.com

[databases]
db01.example.com
db02.example.com

[all:vars]
ansible_user=ansible
ansible_become=yes
ansible_python_interpreter=/usr/bin/python3

25.4 Generate Certificates with Ansible

Playbook: Generate Private Keys

#============================================#
# playbooks/generate-keys.yml
#============================================#

---
- name: Generate Private Keys for RHEL Servers
  hosts: webservers
  become: yes

  tasks:
    - name: Install OpenSSL
      dnf:
        name: openssl
        state: present

    - name: Generate private key
      community.crypto.openssl_privatekey:
        path: "/etc/pki/tls/private/{{ inventory_hostname }}.key"
        size: 2048
        type: RSA
        mode: '0600'
        owner: root
        group: root

    - name: Verify key generated
      stat:
        path: "/etc/pki/tls/private/{{ inventory_hostname }}.key"
      register: key_file

    - name: Show result
      debug:
        msg: "Key generated: {{ key_file.stat.exists }}"

Playbook: Generate CSRs

#============================================#
# playbooks/generate-csrs.yml
#============================================#

---
- name: Generate Certificate Signing Requests
  hosts: webservers
  become: yes

  tasks:
    - name: Generate CSR
      community.crypto.openssl_csr:
        path: "/tmp/{{ inventory_hostname }}.csr"
        privatekey_path: "/etc/pki/tls/private/{{ inventory_hostname }}.key"
        common_name: "{{ inventory_hostname }}"
        subject_alt_name:
          - "DNS:{{ inventory_hostname }}"
          - "DNS:{{ inventory_hostname_short }}"
        key_usage:
          - digitalSignature
          - keyEncipherment
        extended_key_usage:
          - serverAuth
        organization_name: "Example Company"
        country_name: "US"

    - name: Fetch CSR to control node
      fetch:
        src: "/tmp/{{ inventory_hostname }}.csr"
        dest: "csrs/{{ inventory_hostname }}.csr"
        flat: yes

25.5 Deploy Certificates with Ansible

Playbook: Deploy Certificates to Apache

#============================================#
# playbooks/deploy-apache-certs.yml
#============================================#

---
- name: Deploy Certificates to Apache Servers
  hosts: webservers
  become: yes

  vars:
    cert_source_dir: "/path/to/certificates"

  tasks:
    - name: Install Apache and mod_ssl
      dnf:
        name:
          - httpd
          - mod_ssl
        state: present

    - name: Deploy certificate
      copy:
        src: "{{ cert_source_dir }}/{{ inventory_hostname }}.crt"
        dest: "/etc/pki/tls/certs/{{ inventory_hostname }}.crt"
        mode: '0644'
        owner: root
        group: root
      notify: reload apache

    - name: Deploy private key
      copy:
        src: "{{ cert_source_dir }}/{{ inventory_hostname }}.key"
        dest: "/etc/pki/tls/private/{{ inventory_hostname }}.key"
        mode: '0600'
        owner: root
        group: root
        no_log: yes  # Don't log private key
      notify: reload apache

    - name: Configure Apache SSL
      template:
        src: templates/ssl.conf.j2
        dest: /etc/httpd/conf.d/ssl.conf
        mode: '0644'
      notify: reload apache

    - name: Ensure Apache is running
      service:
        name: httpd
        state: started
        enabled: yes

  handlers:
    - name: reload apache
      service:
        name: httpd
        state: reloaded

25.6 Certificate Validation with Ansible

Playbook: Validate Certificates

#============================================#
# playbooks/validate-certificates.yml
#============================================#

---
- name: Validate Certificates on RHEL Servers
  hosts: all
  become: yes

  tasks:
    - name: Find all certificates
      find:
        paths: /etc/pki/tls/certs/
        patterns: '*.crt'
      register: certificates

    - name: Check certificate expiration
      community.crypto.x509_certificate_info:
        path: "{{ item.path }}"
      register: cert_info
      loop: "{{ certificates.files }}"
      loop_control:
        label: "{{ item.path }}"

    - name: Identify expiring certificates (30 days)
      set_fact:
        expiring_certs: "{{ expiring_certs | default([]) + [item.item.path] }}"
      when:
        - (item.not_after | to_datetime('%Y%m%d%H%M%SZ')) - (ansible_date_time.iso8601 | to_datetime) < '30 days'
      loop: "{{ cert_info.results }}"
      loop_control:
        label: "{{ item.item.path }}"

    - name: Report expiring certificates
      debug:
        msg: "⚠️ Certificate expiring soon: {{ item }}"
      loop: "{{ expiring_certs | default([]) }}"
      when: expiring_certs is defined

25.7 certmonger Management with Ansible

Playbook: Setup certmonger Tracking

#============================================#
# playbooks/setup-certmonger.yml
#============================================#

---
- name: Configure certmonger Tracking
  hosts: webservers
  become: yes

  tasks:
    - name: Install certmonger
      dnf:
        name: certmonger
        state: present

    - name: Ensure certmonger is running
      service:
        name: certmonger
        state: started
        enabled: yes

    - name: Request certificate from FreeIPA
      command: >
        ipa-getcert request
        -f /etc/pki/tls/certs/{{ inventory_hostname }}.crt
        -k /etc/pki/tls/private/{{ inventory_hostname }}.key
        -K HTTP/{{ inventory_hostname }}@EXAMPLE.COM
        -D {{ inventory_hostname }}
        -C "systemctl reload httpd"
      args:
        creates: /etc/pki/tls/certs/{{ inventory_hostname }}.crt

    - name: Check certmonger status
      command: getcert list
      register: cert_status
      changed_when: false

    - name: Display status
      debug:
        var: cert_status.stdout_lines

25.8 Certificate Monitoring with Ansible

Playbook: Certificate Expiration Report

#============================================#
# playbooks/cert-expiration-report.yml
#============================================#

---
- name: Generate Certificate Expiration Report
  hosts: all
  become: yes
  gather_facts: yes

  tasks:
    - name: Get certificate expiration for Apache
      shell: |
        if [ -f /etc/pki/tls/certs/{{ inventory_hostname }}.crt ]; then
          openssl x509 -in /etc/pki/tls/certs/{{ inventory_hostname }}.crt -noout -enddate | cut -d= -f2
        else
          echo "No certificate found"
        fi
      register: cert_expiry
      changed_when: false

    - name: Calculate days until expiration
      set_fact:
        days_left: "{{ ((cert_expiry.stdout | to_datetime('%b %d %H:%M:%S %Y %Z')) - (ansible_date_time.iso8601 | to_datetime)).days }}"
      when: cert_expiry.stdout != "No certificate found"

    - name: Add to report
      set_fact:
        cert_report: |
          {{ inventory_hostname }},{{ cert_expiry.stdout }},{{ days_left | default('N/A') }}
      delegate_to: localhost
      delegate_facts: yes

    - name: Save report
      copy:
        content: "{{ hostvars | dict2items | map(attribute='value.cert_report') | join('\n') }}"
        dest: "/tmp/cert-expiration-report.csv"
      delegate_to: localhost
      run_once: yes

25.9 Complete Certificate Deployment Workflow

End-to-End Automation

#============================================#
# playbooks/full-cert-deployment.yml
#============================================#

---
- name: Complete Certificate Deployment
  hosts: webservers
  become: yes

  vars:
    cert_domain: "example.com"
    cert_base_path: "/etc/pki/tls"

  pre_tasks:
    - name: Gather facts
      setup:

  tasks:
    # 1. Ensure directories exist
    - name: Ensure certificate directories exist
      file:
        path: "{{ item }}"
        state: directory
        mode: '0755'
      loop:
        - "{{ cert_base_path }}/certs"
        - "{{ cert_base_path }}/private"

    # 2. Generate private key (if not exists)
    - name: Generate private key
      community.crypto.openssl_privatekey:
        path: "{{ cert_base_path }}/private/{{ inventory_hostname }}.key"
        size: 2048
        mode: '0600'

    # 3. Deploy certificate (from controller)
    - name: Deploy certificate
      copy:
        src: "files/certs/{{ inventory_hostname }}.crt"
        dest: "{{ cert_base_path }}/certs/{{ inventory_hostname }}.crt"
        mode: '0644'
      notify: reload httpd

    # 4. Deploy CA bundle
    - name: Deploy CA bundle
      copy:
        src: "files/ca-bundle.crt"
        dest: "{{ cert_base_path }}/certs/ca-bundle.crt"
        mode: '0644'

    # 5. Configure Apache
    - name: Deploy Apache SSL configuration
      template:
        src: templates/apache-ssl.conf.j2
        dest: /etc/httpd/conf.d/ssl.conf
        mode: '0644'
      notify: reload httpd

    # 6. Validate configuration
    - name: Test Apache configuration
      command: apachectl configtest
      changed_when: false

    # 7. Ensure firewall open
    - name: Open HTTPS in firewall
      firewalld:
        service: https
        permanent: yes
        state: enabled
        immediate: yes

    # 8. Ensure Apache running
    - name: Ensure Apache is running
      service:
        name: httpd
        state: started
        enabled: yes

  handlers:
    - name: reload httpd
      service:
        name: httpd
        state: reloaded

25.10 Ansible Roles for Certificates

Create Reusable Role

#============================================#
# CREATE ANSIBLE ROLE FOR CERTIFICATES
#============================================#

# Create role structure
ansible-galaxy role init certificates

# Directory structure:
certificates/
├── defaults/
│   └── main.yml          # Default variables
├── files/
│   └── ca-bundle.crt     # CA certificates
├── handlers/
│   └── main.yml          # Service reload handlers
├── tasks/
│   └── main.yml          # Main tasks
├── templates/
│   └── ssl.conf.j2       # Config templates
└── vars/
    └── main.yml          # Variables

Example Role Tasks

#============================================#
# roles/certificates/tasks/main.yml
#============================================#

---
- name: Install certificate management tools
  dnf:
    name:
      - openssl
      - certmonger
    state: present

- name: Ensure certificate directories
  file:
    path: "{{ item }}"
    state: directory
    mode: '0755'
  loop:
    - /etc/pki/tls/certs
    - /etc/pki/tls/private

- name: Deploy certificates
  include_tasks: deploy-cert.yml
  loop: "{{ certificates }}"
  loop_control:
    loop_var: cert

- name: Setup certmonger tracking
  include_tasks: setup-certmonger.yml
  when: use_certmonger | default(false)

Use the Role

#============================================#
# playbook.yml
#============================================#

---
- name: Deploy Certificates
  hosts: webservers
  become: yes

  roles:
    - role: certificates
      vars:
        certificates:
          - name: "{{ inventory_hostname }}"
            service: httpd
            principal: "HTTP/{{ inventory_hostname }}@REALM"

25.11 Best Practices

Ansible Certificate Management Best Practices

✅ **Use community.crypto collection** for certificate tasks
✅ **Vault-encrypt private keys** (ansible-vault)
✅ **Use no_log for sensitive data** (keys, passwords)
✅ **Test on staging first** before production
✅ **Use handlers** for service reloads (avoid unnecessary restarts)
✅ **Make playbooks idempotent** (safe to run multiple times)
✅ **Version control** playbooks in Git
✅ **Document variables** and requirements
✅ **Tag tasks** for selective execution
✅ **Use roles** for reusability

Security Considerations

# Encrypt private keys with ansible-vault
ansible-vault encrypt files/private-keys/*.key

# Use no_log for sensitive tasks
- name: Deploy private key
  copy:
    src: "{{ key_file }}"
    dest: "/etc/pki/tls/private/server.key"
  no_log: yes

# Use vault for passwords
# group_vars/all/vault.yml (encrypted)
admin_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          ...

25.12 Complete Examples

Example 1: Deploy CA to All Servers

---
- name: Deploy Corporate CA to All RHEL Servers
  hosts: all
  become: yes

  tasks:
    - name: Copy CA certificate
      copy:
        src: files/corporate-ca.crt
        dest: /etc/pki/ca-trust/source/anchors/corporate-ca.crt
        mode: '0644'

    - name: Update CA trust
      command: update-ca-trust extract
      changed_when: true

    - name: Verify CA installed
      command: trust list
      register: trust_list
      changed_when: false

    - name: Confirm CA present
      assert:
        that:
          - "'Corporate CA' in trust_list.stdout"
        fail_msg: "Corporate CA not in trust store!"

Example 2: Mass Certificate Renewal Check

---
- name: Check Certificate Expiration Across Fleet
  hosts: all
  become: yes

  tasks:
    - name: Check certmonger status
      command: getcert list
      register: certmonger_status
      changed_when: false
      failed_when: false

    - name: Check for CA_UNREACHABLE
      set_fact:
        has_issue: true
      when: "'CA_UNREACHABLE' in certmonger_status.stdout"

    - name: Report problems
      debug:
        msg: "⚠️ {{ inventory_hostname }} has CA_UNREACHABLE certificates!"
      when: has_issue | default(false)

    - name: Create problem report
      lineinfile:
        path: "/tmp/cert-problems.txt"
        line: "{{ inventory_hostname }}: CA_UNREACHABLE"
        create: yes
      delegate_to: localhost
      when: has_issue | default(false)

25.13 Key Takeaways

  1. Ansible enables mass certificate deployment
  2. community.crypto collection essential for certificate tasks
  3. Use ansible-vault for private keys
  4. Idempotent playbooks are critical
  5. Test on staging before production
  6. Combine with certmonger for best results
  7. Version control everything in Git

Quick Reference Card

┌────────────────────────────────────────────────────────────────────┐
│ ANSIBLE CERTIFICATE AUTOMATION QUICK REFERENCE                     │
├────────────────────────────────────────────────────────────────────┤
│ Install:        dnf install ansible-core                           │
│ Collection:     ansible-galaxy collection install community.crypto │
│                                                                    │
│ Generate key:   community.crypto.openssl_privatekey                │
│ Generate CSR:   community.crypto.openssl_csr                       │
│ Get cert info:  community.crypto.x509_certificate_info             │
│                                                                    │
│ Deploy cert:    copy module (with mode: '0644')                    │
│ Deploy key:     copy module (with mode: '0600', no_log: yes)       │
│                                                                    │
│ Vault:          ansible-vault encrypt <file>                       │
│                 ansible-vault decrypt <file>                       │
│                                                                    │
│ Run:            ansible-playbook playbook.yml                      │
│ Dry run:        ansible-playbook playbook.yml --check              │
│ Specific:       ansible-playbook playbook.yml --tags certs         │
└────────────────────────────────────────────────────────────────────┘

✅ Use community.crypto collection
✅ Encrypt private keys with ansible-vault
✅ Use no_log for sensitive data

🧪 Hands-On Lab

Lab 14: Ansible Automation

Automate certificate deployment with Ansible

  • 📁 Location: labs/en_US/14-ansible-automation/
  • ⏱️ Time: 40-50 minutes
  • 🎯 Level: Advanced

Chapter Navigation

Chapter 26: Monitoring & Alerting on RHEL

Prevent Outages: Proactive monitoring prevents certificate expiration surprises. Learn how to monitor certificates and set up alerts on RHEL.


26.1 Why Monitor Certificates?

Without Monitoring:

❌ Certificate expires
❌ Website goes down at 2 AM
❌ Emergency incident
❌ Revenue loss
❌ Reputation damage
❌ Stressful night for ops team

With Monitoring:

✅ Alert 30 days before expiry
✅ Planned renewal during business hours
✅ No outages
✅ No emergencies
✅ Happy customers
✅ Well-rested ops team

26.2 What to Monitor

Certificate Metrics

## Critical Metrics:

✅ **Expiration date** (days until expiry)
✅ **Certificate validity** (not yet valid, expired)
✅ **Certificate/key pair match**
✅ **Trust chain validity**
✅ **certmonger status** (if used)
✅ **Service health** (using the certificate)
✅ **Renewal success/failure**

## Warning Thresholds:

🟡 60 days: First warning
🟠 30 days: Second warning
🔴 15 days: Critical alert
🚨 7 days: Emergency escalation

26.3 Simple Monitoring Scripts

Basic Expiration Check

#!/bin/bash
# check-cert-expiration.sh
# Simple certificate expiration checker

WARN_DAYS=30
CRIT_DAYS=7

check_cert() {
  local cert=$1
  local name=$(basename "$cert")

  if [ ! -f "$cert" ]; then
    echo "❌ $name: File not found"
    return 2
  fi

  # Get expiration date
  expiry=$(openssl x509 -in "$cert" -noout -enddate 2>/dev/null | cut -d= -f2)

  if [ -z "$expiry" ]; then
    echo "❌ $name: Invalid certificate"
    return 2
  fi

  # Calculate days remaining
  expiry_epoch=$(date -d "$expiry" +%s 2>/dev/null)
  now_epoch=$(date +%s)
  days_left=$(( ($expiry_epoch - $now_epoch) / 86400 ))

  # Check thresholds
  if [ $days_left -lt 0 ]; then
    echo "🚨 $name: EXPIRED $((- days_left)) days ago!"
    return 2
  elif [ $days_left -lt $CRIT_DAYS ]; then
    echo "🔴 $name: CRITICAL - $days_left days left"
    return 2
  elif [ $days_left -lt $WARN_DAYS ]; then
    echo "🟡 $name: WARNING - $days_left days left"
    return 1
  else
    echo "✅ $name: OK - $days_left days left"
    return 0
  fi
}

# Check all certificates
for cert in /etc/pki/tls/certs/*.crt; do
  check_cert "$cert"
done

certmonger Status Monitor

#!/bin/bash
# monitor-certmonger.sh
# Monitor certmonger tracking status

echo "=== certmonger Status Monitor ==="

# Check if certmonger is running
if ! systemctl is-active --quiet certmonger; then
  echo "🚨 CRITICAL: certmonger is not running!"
  systemctl status certmonger
  exit 2
fi

# Get certmonger status
STATUS_OUTPUT=$(sudo getcert list 2>&1)

# Count certificates by status
TOTAL=$(echo "$STATUS_OUTPUT" | grep -c "Request ID")
MONITORING=$(echo "$STATUS_OUTPUT" | grep -c "status: MONITORING")
UNREACHABLE=$(echo "$STATUS_OUTPUT" | grep -c "status: CA_UNREACHABLE")
REJECTED=$(echo "$STATUS_OUTPUT" | grep -c "status: CA_REJECTED")

echo "Total certificates: $TOTAL"
echo "  MONITORING: $MONITORING ✅"
echo "  CA_UNREACHABLE: $UNREACHABLE $([ $UNREACHABLE -gt 0 ] && echo '⚠️')"
echo "  CA_REJECTED: $REJECTED $([ $REJECTED -gt 0 ] && echo '❌')"

# Alert if problems
if [ $UNREACHABLE -gt 0 ] || [ $REJECTED -gt 0 ]; then
  echo ""
  echo "🚨 ATTENTION REQUIRED:"
  sudo getcert list | grep -B5 "status: CA_" | grep -E "(Request ID|status:)"
  exit 1
fi

echo "✅ All certificates OK"

26.4 Systemd Timer for Monitoring

Create Monitoring Timer

#============================================#
# CREATE SYSTEMD TIMER FOR MONITORING
#============================================#

# Create service file
sudo tee /etc/systemd/system/cert-monitor.service << 'EOF'
[Unit]
Description=Certificate Expiration Monitor
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/check-cert-expiration.sh
StandardOutput=journal
StandardError=journal
EOF

# Create timer file (run daily)
sudo tee /etc/systemd/system/cert-monitor.timer << 'EOF'
[Unit]
Description=Daily Certificate Monitoring
Requires=cert-monitor.service

[Timer]
OnCalendar=daily
OnBootSec=15min
Persistent=true

[Install]
WantedBy=timers.target
EOF

# Install monitoring script
sudo cp check-cert-expiration.sh /usr/local/bin/
sudo chmod +x /usr/local/bin/check-cert-expiration.sh

# Enable timer
sudo systemctl daemon-reload
sudo systemctl enable cert-monitor.timer
sudo systemctl start cert-monitor.timer

# Verify
systemctl list-timers | grep cert-monitor

# Test manually
sudo systemctl start cert-monitor.service
sudo journalctl -u cert-monitor.service

26.5 Email Alerts

Simple Email Alerting

#!/bin/bash
# cert-monitor-with-email.sh
# Certificate monitor with email alerts

EMAIL="admin@example.com"
WARN_DAYS=30

ALERTS=""

for cert in /etc/pki/tls/certs/*.crt; do
  [ -f "$cert" ] || continue

  if ! openssl x509 -in "$cert" -noout -checkend $((86400*WARN_DAYS)) 2>/dev/null; then
    expiry=$(openssl x509 -in "$cert" -noout -enddate | cut -d= -f2)
    ALERTS="$ALERTS\nCertificate: $cert\nExpires: $expiry\n"
  fi
done

if [ -n "$ALERTS" ]; then
  echo -e "Certificate Expiration Warnings:\n$ALERTS" | \
    mail -s "⚠️ Certificate Expiration Alert - $(hostname)" "$EMAIL"
fi

26.6 Prometheus Monitoring (Advanced)

Certificate Exporter

#============================================#
# PROMETHEUS CERTIFICATE MONITORING
#============================================#

# Install x509-certificate-exporter (example)
# https://github.com/enix/x509-certificate-exporter

# Or use Node Exporter textfile collector

# Create metrics collector script
cat > /usr/local/bin/cert-metrics.sh << 'EOF'
#!/bin/bash
# Generate Prometheus metrics for certificates

METRICS_FILE="/var/lib/node_exporter/textfile_collector/certificates.prom"
mkdir -p $(dirname "$METRICS_FILE")

cat > "$METRICS_FILE" << PROM
# HELP certificate_expiry_days Days until certificate expiration
# TYPE certificate_expiry_days gauge
PROM

for cert in /etc/pki/tls/certs/*.crt; do
  [ -f "$cert" ] || continue

  expiry=$(openssl x509 -in "$cert" -noout -enddate 2>/dev/null | cut -d= -f2)
  expiry_epoch=$(date -d "$expiry" +%s 2>/dev/null)
  now_epoch=$(date +%s)
  days_left=$(( ($expiry_epoch - $now_epoch) / 86400 ))

  echo "certificate_expiry_days{cert=\"$cert\",hostname=\"$(hostname)\"} $days_left" >> "$METRICS_FILE"
done
EOF

chmod +x /usr/local/bin/cert-metrics.sh

# Run via cron
echo "*/5 * * * * /usr/local/bin/cert-metrics.sh" | sudo crontab -

Prometheus Alert Rules

#============================================#
# prometheus-alerts.yml
#============================================#

groups:
  - name: certificates
    rules:
      - alert: CertificateExpiringSoon
        expr: certificate_expiry_days < 30
        for: 1h
        labels:
          severity: warning
        annotations:
          summary: "Certificate expiring in {{ $value }} days"
          description: "Certificate {{ $labels.cert }} on {{ $labels.hostname }} expires in {{ $value }} days"

      - alert: CertificateExpiryCritical
        expr: certificate_expiry_days < 7
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Certificate expiring in {{ $value }} days!"
          description: "URGENT: Certificate {{ $labels.cert }} on {{ $labels.hostname }} expires in {{ $value }} days!"

      - alert: CertificateExpired
        expr: certificate_expiry_days < 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "Certificate EXPIRED!"
          description: "Certificate {{ $labels.cert }} on {{ $labels.hostname }} has expired!"

26.7 Centralized Logging

Send Certificate Events to Syslog

#============================================#
# LOG CERTIFICATE EVENTS
#============================================#

# certmonger logs to journal
sudo journalctl -u certmonger -f

# Forward to central syslog
# /etc/rsyslog.conf
*.* @@syslog-server.example.com:514

# Or configure specific certmonger logging
# Monitor certmonger renewals
sudo journalctl -u certmonger --since today | grep -i "renewed\|failed"

26.8 Monitoring Tools Comparison

Options for RHEL

ToolComplexityCostIntegrationAlerting
Simple scriptsLowFreeEasyEmail/syslog
Nagios/IcingaMediumFreeGoodMultiple
Prometheus + GrafanaMedium-HighFreeExcellentPowerful
ZabbixMediumFreeGoodMultiple
Commercial (Datadog, etc.)Low$$$ExcellentAdvanced
Red Hat InsightsLowSubscriptionNativeDashboard

Recommendation for RHEL:

  • Small: Simple scripts + email
  • Medium: Prometheus + Grafana
  • Enterprise: Commercial or Red Hat Insights

26.9 Complete Monitoring Solution

Comprehensive Monitoring Script

#!/bin/bash
# comprehensive-cert-monitor.sh
# Complete certificate monitoring for RHEL

WARN_DAYS=30
CRIT_DAYS=7
EMAIL="admin@example.com"
LOG_FILE="/var/log/cert-monitor.log"

log() {
  echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE"
}

send_alert() {
  local subject=$1
  local body=$2
  echo "$body" | mail -s "$subject" "$EMAIL"
  logger -t cert-monitor "$subject"
}

log "=== Starting Certificate Monitor ==="

# Check 1: certmonger running?
if ! systemctl is-active --quiet certmonger; then
  send_alert "🚨 certmonger NOT running on $(hostname)" \
    "certmonger service is not running. Certificate renewals may fail!"
fi

# Check 2: certmonger status
UNREACHABLE=$(sudo getcert list | grep -c "CA_UNREACHABLE")
if [ $UNREACHABLE -gt 0 ]; then
  send_alert "⚠️ CA unreachable for $UNREACHABLE certificates on $(hostname)" \
    "$(sudo getcert list | grep -B5 'CA_UNREACHABLE')"
fi

# Check 3: Certificate expiration
CRITICAL=0
WARNING=0

for cert in /etc/pki/tls/certs/*.crt; do
  [ -f "$cert" ] || continue

  expiry=$(openssl x509 -in "$cert" -noout -enddate 2>/dev/null | cut -d= -f2)
  [ -z "$expiry" ] && continue

  expiry_epoch=$(date -d "$expiry" +%s 2>/dev/null)
  now_epoch=$(date +%s)
  days_left=$(( ($expiry_epoch - $now_epoch) / 86400 ))

  if [ $days_left -lt 0 ]; then
    log "🚨 EXPIRED: $cert"
    ((CRITICAL++))
  elif [ $days_left -lt $CRIT_DAYS ]; then
    log "🔴 CRITICAL ($days_left days): $cert"
    ((CRITICAL++))
  elif [ $days_left -lt $WARN_DAYS ]; then
    log "🟡 WARNING ($days_left days): $cert"
    ((WARNING++))
  else
    log "✅ OK ($days_left days): $cert"
  fi
done

# Send summary alert if issues
if [ $CRITICAL -gt 0 ] || [ $WARNING -gt 0 ]; then
  SUMMARY="Critical: $CRITICAL, Warning: $WARNING\n\n$(tail -20 $LOG_FILE)"
  send_alert "Certificate Alert: $(hostname)" "$SUMMARY"
fi

log "=== Monitor Complete: Critical=$CRITICAL, Warning=$WARNING ==="

26.10 Monitoring with Grafana

Dashboard Example

{
  "dashboard": {
    "title": "RHEL Certificate Monitoring",
    "panels": [
      {
        "title": "Certificates Expiring Soon",
        "targets": [
          {
            "expr": "certificate_expiry_days < 30"
          }
        ]
      },
      {
        "title": "certmonger Status",
        "targets": [
          {
            "expr": "certmonger_status != 'MONITORING'"
          }
        ]
      }
    ]
  }
}

26.11 Key Takeaways

  1. Monitor proactively - Don’t wait for expiration
  2. 30-day warning minimum recommended
  3. certmonger status critical if using automation
  4. Multiple alert channels (email, Slack, PagerDuty)
  5. Test monitoring - Ensure alerts actually reach you
  6. Log everything for audit trail
  7. Automate remediation where possible

Quick Reference Card

┌─────────────────────────────────────────────────────────────────┐
│ CERTIFICATE MONITORING QUICK REFERENCE                          │
├─────────────────────────────────────────────────────────────────┤
│ Check expiry:  openssl x509 -in cert.crt -noout -checkend 86400 │
│ Days left:     openssl x509 -in cert.crt -noout -enddate        │
│                                                                 │
│ certmonger:    getcert list                                     │
│ Status:        getcert list | grep "status:"                    │
│ Logs:          journalctl -u certmonger                         │
│                                                                 │
│ Alert levels:  60 days (info)                                   │
│                30 days (warning)                                │
│                7 days (critical)                                │
│                0 days (emergency!)                              │
│                                                                 │
│ Tools:         Simple scripts, Prometheus, Nagios, Zabbix       │
│ Native:        certmonger built-in tracking                     │
└─────────────────────────────────────────────────────────────────┘

✅ Monitor == No Surprises!
✅ Automate monitoring with systemd timers or cron

Chapter Navigation

Chapter 27: RHEL Certificate Troubleshooting Methodology

Critical Skill: This chapter teaches you a systematic approach to troubleshoot ANY certificate issue on RHEL systems. Master this methodology and you’ll solve problems in minutes instead of hours.


27.1 The Problem

Certificate issues are frustrating:

  • Error messages are cryptic
  • Root causes are hidden
  • Multiple layers involved (OpenSSL, service config, SELinux, crypto-policies)
  • Varies by RHEL version

Random troubleshooting doesn’t work. You need a system.


27.2 The Systematic Approach

Follow this 7-step methodology for EVERY certificate issue:

Step 1: Identify RHEL Version & Environment
Step 2: Verify Basic Certificate Properties
Step 3: Check Trust Chain & CA Validation
Step 4: Verify Service Configuration
Step 5: Check System-Level Settings
Step 6: Test Certificate Functionality
Step 7: Review Logs & Error Details

Rule: Never skip steps. Each provides critical diagnostic information.


27.3 Step 1: Identify RHEL Version & Environment

Why It Matters

Certificate behavior differs significantly across RHEL versions.

Quick Checks

#============================================#
# RHEL VERSION CHECK
#============================================#

# 1. Check RHEL version
cat /etc/redhat-release
# Output example: Red Hat Enterprise Linux release 9.8 (Plow)

# 2. Check OpenSSL version
openssl version
# RHEL 7: OpenSSL 1.0.2k
# RHEL 8: OpenSSL 1.1.1k
# RHEL 9/10: OpenSSL 3.5.5

# 3. Check crypto-policy (RHEL 8+)
update-crypto-policies --show 2>/dev/null
# DEFAULT, LEGACY, FUTURE, or FIPS

# 4. Check FIPS mode
fips-mode-setup --check 2>/dev/null
# FIPS mode is enabled/disabled

# 5. Check SELinux status
getenforce
# Enforcing, Permissive, or Disabled

What to Document

Create a troubleshooting note with:

RHEL Version: _______
OpenSSL Version: _______
Crypto-Policy: _______ (if RHEL 8+)
FIPS Mode: _______
SELinux: _______
Service: _______ (Apache, NGINX, Postfix, etc.)

27.4 Step 2: Verify Basic Certificate Properties

The Five Essential Checks

#============================================#
# CHECK 1: Certificate Expiration
#============================================#

# View certificate dates
openssl x509 -in /path/to/cert.crt -noout -dates

# Output:
# notBefore=Jan 1 00:00:00 2024 GMT
# notAfter=Jan 1 23:59:59 2025 GMT   ← Must be in the future!

# Quick check if expired
openssl x509 -in /path/to/cert.crt -noout -checkend 0
# Exit 0 = valid, Exit 1 = expired

# Check expiration in X days
openssl x509 -in /path/to/cert.crt -noout -checkend $((86400*30))
# Check if expires in next 30 days


#============================================#
# CHECK 2: Certificate Subject/Hostname
#============================================#

# View subject (who certificate is for)
openssl x509 -in /path/to/cert.crt -noout -subject
# subject=CN=server.example.com

# View Subject Alternative Names (SANs) - CRITICAL!
openssl x509 -in /path/to/cert.crt -noout -ext subjectAltName
# X509v3 Subject Alternative Name:
#     DNS:server.example.com, DNS:www.example.com

# ⚠️ Modern browsers REQUIRE SANs (CN alone is insufficient)


#============================================#
# CHECK 3: Certificate/Key Pair Match
#============================================#

# Get certificate modulus
openssl x509 -noout -modulus -in /path/to/cert.crt | openssl md5

# Get key modulus
openssl rsa -noout -modulus -in /path/to/cert.key | openssl md5

# ✅ If MD5 hashes match → cert and key are paired
# ❌ If different → WRONG KEY!


#============================================#
# CHECK 4: Certificate Issuer
#============================================#

# View who signed this certificate
openssl x509 -in /path/to/cert.crt -noout -issuer
# issuer=C=US, O=Let's Encrypt, CN=R3

# Self-signed? (subject == issuer)
openssl x509 -in /path/to/cert.crt -noout -subject -issuer | sort | uniq -d
# If output is not empty → self-signed


#============================================#
# CHECK 5: Certificate Algorithm & Key Size
#============================================#

# View signature algorithm
openssl x509 -in /path/to/cert.crt -noout -text | grep "Signature Algorithm"
# Signature Algorithm: sha256WithRSAEncryption   ← Good
# Signature Algorithm: sha1WithRSAEncryption     ← Bad (deprecated)

# View public key size
openssl x509 -in /path/to/cert.crt -noout -text | grep "Public-Key"
# Public-Key: (2048 bit)   ← Minimum
# Public-Key: (4096 bit)   ← Better

# ⚠️ RHEL 8+ rejects < 2048 bit keys by default
# ⚠️ RHEL 9+ rejects SHA-1 signatures by default

Quick Validation Script

#!/bin/bash
# quick-cert-check.sh
CERT=$1

echo "=== Certificate Quick Check ==="
echo ""
echo "File: $CERT"
echo ""

echo "1. Expiration:"
openssl x509 -in "$CERT" -noout -dates

echo ""
echo "2. Subject:"
openssl x509 -in "$CERT" -noout -subject

echo ""
echo "3. SANs:"
openssl x509 -in "$CERT" -noout -ext subjectAltName 2>/dev/null || echo "No SANs found"

echo ""
echo "4. Issuer:"
openssl x509 -in "$CERT" -noout -issuer

echo ""
echo "5. Algorithm & Key:"
openssl x509 -in "$CERT" -noout -text | grep -E "(Signature Algorithm|Public-Key)"

echo ""
echo "6. Still valid?"
if openssl x509 -in "$CERT" -noout -checkend 0 >/dev/null 2>&1; then
    echo "✅ Certificate is valid"
else
    echo "❌ Certificate has expired!"
fi

Usage:

bash quick-cert-check.sh /etc/pki/tls/certs/server.crt

27.5 Step 3: Check Trust Chain & CA Validation

Understanding the Chain

Root CA (must be trusted by system)
  └─ Intermediate CA(s)
      └─ Server Certificate (your certificate)

Verify Trust Chain

#============================================#
# CHECK COMPLETE CERTIFICATE CHAIN
#============================================#

# Method 1: Verify against system CA bundle
openssl verify /path/to/cert.crt
# /path/to/cert.crt: OK   ← Good!
# error 20: unable to get local issuer certificate   ← Missing CA!

# Method 2: Verify with specific CA file
openssl verify -CAfile /etc/pki/tls/certs/ca-bundle.crt /path/to/cert.crt

# Method 3: Show full chain
openssl s_client -connect server.example.com:443 -showcerts


#============================================#
# CHECK IF CA IS TRUSTED BY RHEL
#============================================#

# List all trusted CAs
trust list | grep -i "certificate-authority"

# Search for specific CA
trust list | grep -i "Let's Encrypt"

# Check if specific CA file is trusted
trust list --filter=ca-anchors | grep -A5 "pkcs11"


#============================================#
# VIEW CERTIFICATE CHAIN
#============================================#

# Extract and view full chain from server
openssl s_client -connect server.example.com:443 -showcerts 2>/dev/null | \
  awk '/BEGIN CERT/,/END CERT/ {print}'

# View chain from file (if bundled)
openssl crl2pkcs7 -nocrl -certfile /path/to/chain.crt | \
  openssl pkcs7 -print_certs -text -noout


#============================================#
# CHECK INTERMEDIATE CERTIFICATES
#============================================#

# Common issue: Missing intermediate certificate!
# Server should send: [Server Cert] → [Intermediate] → [Root]
# But only sends: [Server Cert]
# Result: Client can't validate chain!

# Test from another machine
echo | openssl s_client -connect server.example.com:443 -servername server.example.com 2>&1 | \
  grep -E "(verify return code|Verify return code)"
# Verify return code: 0 (ok)   ← Good
# Verify return code: 21 (unable to verify the first certificate)   ← Missing intermediate!

Common Trust Issues

Error CodeMeaningSolution
0OK✅ No issues
19Self-signed certificate in chainAdd CA to trust store
20Unable to get local issuer certMissing CA or intermediate
21Unable to verify first certificateMissing intermediate cert
27Certificate not trustedCA not in system trust store

27.6 Step 4: Verify Service Configuration

Service-Specific Checks

#============================================#
# APACHE (httpd)
#============================================#

# Check SSL configuration
sudo apachectl -t -D DUMP_VHOSTS | grep -A5 ":443"

# View SSL cert paths
sudo grep -r "SSLCertificateFile\|SSLCertificateKeyFile" /etc/httpd/

# Test configuration syntax
sudo apachectl configtest

# Check SSL modules loaded
sudo httpd -M | grep ssl


#============================================#
# NGINX
#============================================#

# Test configuration
sudo nginx -t

# View SSL paths
sudo grep -r "ssl_certificate\|ssl_certificate_key" /etc/nginx/

# Check certificate files referenced
sudo nginx -T | grep "ssl_certificate"


#============================================#
# POSTFIX (Mail)
#============================================#

# View TLS settings
sudo postconf | grep -i tls

# Check cert/key paths
sudo postconf smtpd_tls_cert_file smtpd_tls_key_file

# Test TLS
openssl s_client -connect localhost:25 -starttls smtp


#============================================#
# OPENLDAP
#============================================#

# Check TLS settings
sudo grep -i "TLSCert\|TLSKey" /etc/openldap/slapd.conf /etc/openldap/slapd.d/* 2>/dev/null

# Test LDAPS
openssl s_client -connect localhost:636


#============================================#
# POSTGRESQL
#============================================#

# Check SSL settings
sudo -u postgres psql -c "SHOW ssl_cert_file; SHOW ssl_key_file;"

# Test SSL connection
psql "host=localhost sslmode=require"

File Permissions Check

#============================================#
# VERIFY PERMISSIONS (CRITICAL!)
#============================================#

# Certificate files (public) should be readable
ls -l /etc/pki/tls/certs/*.crt
# -rw-r--r-- (644)   ← Good

# Key files (private) should be protected
ls -l /etc/pki/tls/private/*.key
# -rw------- (600) or -rw-r----- (640)   ← Good
# -rw-r--r-- (644)   ← BAD! Too permissive!

# Check ownership
ls -l /etc/pki/tls/private/*.key
# Should be owned by service user or root

# Fix permissions if needed
sudo chmod 600 /etc/pki/tls/private/server.key
sudo chown root:root /etc/pki/tls/private/server.key

27.7 Step 5: Check System-Level Settings

RHEL Version-Specific

#============================================#
# RHEL 8/9/10: CHECK CRYPTO-POLICIES
#============================================#

# Current policy
update-crypto-policies --show
# DEFAULT, LEGACY, FUTURE, or FIPS

# If service fails with "no shared cipher" or similar:
# Temporarily test with LEGACY policy
sudo update-crypto-policies --set LEGACY
sudo systemctl restart <service>

# Test if it works now
# If YES → cipher/TLS version incompatibility
# If NO → different issue

# Revert to DEFAULT
sudo update-crypto-policies --set DEFAULT


#============================================#
# CHECK FIPS MODE (All Versions)
#============================================#

fips-mode-setup --check
# FIPS mode is enabled.

# In FIPS mode, additional restrictions apply:
# - Only approved algorithms
# - Stricter key requirements
# - Some ciphers disabled


#============================================#
# CHECK SELINUX (Critical!)
#============================================#

# SELinux status
getenforce
# Enforcing, Permissive, or Disabled

# Check for certificate-related denials
sudo ausearch -m avc -ts recent | grep -i cert

# Check SELinux context of cert files
ls -Z /etc/pki/tls/certs/server.crt
# system_u:object_r:cert_t:s0   ← Correct

# If wrong, relabel
sudo restorecon -v /etc/pki/tls/certs/server.crt
sudo restorecon -v /etc/pki/tls/private/server.key


#============================================#
# CHECK FIREWALL
#============================================#

# Verify service port is open
sudo firewall-cmd --list-all | grep -E "(https|443|ldaps|636)"

# If not open
sudo firewall-cmd --add-service=https --permanent
sudo firewall-cmd --reload

27.8 Step 6: Test Certificate Functionality

Live Connection Tests

#============================================#
# TEST HTTPS/TLS CONNECTION
#============================================#

# Method 1: openssl s_client (most detailed)
openssl s_client -connect server.example.com:443 -servername server.example.com

# Look for:
# - "Verify return code: 0 (ok)"   ← Good
# - Certificate chain display
# - Cipher negotiated
# - Protocol version (TLS 1.2/1.3)

# Method 2: curl (quick test)
curl -v https://server.example.com/
# Look for:
# * SSL connection using TLSv1.3
# * Server certificate:
# *  subject: CN=server.example.com
# *  issuer: CN=Let's Encrypt Authority

# Method 3: Test with specific TLS version
openssl s_client -connect server.example.com:443 -tls1_2
openssl s_client -connect server.example.com:443 -tls1_3


#============================================#
# TEST FROM CLIENT PERSPECTIVE
#============================================#

# Test DNS resolution
nslookup server.example.com
# Must resolve to correct IP

# Test network connectivity
telnet server.example.com 443
nc -zv server.example.com 443

# Test with different clients
curl --insecure https://server.example.com/   # Bypass cert check
wget --no-check-certificate https://server.example.com/

Certificate Validation Tests

#============================================#
# VALIDATE SPECIFIC ASPECTS
#============================================#

# Test hostname matching
openssl s_client -connect server.example.com:443 -servername server.example.com 2>&1 | \
  grep "verify return"

# Test with wrong hostname (should fail)
openssl s_client -connect server.example.com:443 -servername wrong.example.com 2>&1 | \
  grep "verify return"

# Test expiration
openssl s_client -connect server.example.com:443 2>&1 | openssl x509 -noout -dates

# Test cipher strength
openssl s_client -connect server.example.com:443 -cipher 'HIGH:!aNULL:!MD5'

27.9 Step 7: Review Logs & Error Details

Where to Look

#============================================#
# SERVICE LOGS
#============================================#

# Apache
sudo tail -f /var/log/httpd/error_log
sudo tail -f /var/log/httpd/ssl_error_log

# NGINX
sudo tail -f /var/log/nginx/error.log

# Postfix
sudo tail -f /var/log/maillog

# System journal (all services)
sudo journalctl -u httpd.service -f
sudo journalctl -u nginx.service -f
sudo journalctl -xe | grep -i cert


#============================================#
# CERTMONGER LOGS (Auto-Renewal)
#============================================#

# certmonger status
sudo getcert list

# certmonger logs
sudo journalctl -u certmonger.service -f

# Detailed status for specific cert
sudo getcert list -i <request-id>


#============================================#
# SELINUX DENIALS
#============================================#

# Recent AVC denials
sudo ausearch -m avc -ts recent

# Certificate-related denials
sudo ausearch -m avc -ts today | grep -i cert


#============================================#
# OPENSSL/TLS ERRORS
#============================================#

# Common in logs:
# - "SSL_CTX_use_certificate:ca md too weak" → Weak signature algorithm
# - "unable to get local issuer certificate" → Missing CA
# - "certificate has expired" → Expired cert
# - "certificate verify failed" → Chain validation failure
# - "no shared cipher" → Cipher mismatch
# - "wrong version number" → Protocol mismatch

27.10 Decision Trees

Quick Diagnostic Flowchart

Certificate Issue
    │
    ├─ Service won't start?
    │   ├─ Check file paths in config
    │   ├─ Check file permissions (600 for keys)
    │   ├─ Check cert/key pair match
    │   └─ Check SELinux context
    │
    ├─ Connection fails with "certificate verify failed"?
    │   ├─ Check CA trust (Step 3)
    │   ├─ Check intermediate certificates
    │   └─ Check crypto-policy (RHEL 8+)
    │
    ├─ "Certificate has expired"?
    │   ├─ Verify with: openssl x509 -noout -dates
    │   ├─ Check certmonger status
    │   └─ Renew certificate
    │
    ├─ "Hostname does not match"?
    │   ├─ Check SANs: openssl x509 -noout -ext subjectAltName
    │   ├─ Verify DNS resolution
    │   └─ Check server_name/ServerName directive
    │
    └─ "No shared cipher" / "wrong version number"?
        ├─ Check crypto-policy (RHEL 8+)
        ├─ Check TLS version compatibility
        └─ Test with: openssl s_client -tls1_2

27.11 Troubleshooting Toolkit

Essential Commands

# Quick reference card for troubleshooting

# 1. IDENTIFY
cat /etc/redhat-release
openssl version
update-crypto-policies --show

# 2. VERIFY CERTIFICATE
openssl x509 -in cert.crt -noout -text
openssl x509 -in cert.crt -noout -dates
openssl x509 -in cert.crt -noout -subject

# 3. CHECK TRUST
openssl verify cert.crt
trust list | grep -i "authority"

# 4. TEST CONNECTION
openssl s_client -connect host:443 -servername host
curl -v https://host/

# 5. CHECK PERMISSIONS
ls -lZ /etc/pki/tls/certs/cert.crt
ls -lZ /etc/pki/tls/private/key.key

# 6. CHECK LOGS
sudo journalctl -xe | grep -i cert
sudo tail -f /var/log/httpd/ssl_error_log

Create Your Troubleshooting Checklist

## Certificate Issue Troubleshooting Checklist

### Environment
- [ ] RHEL Version: _______
- [ ] OpenSSL Version: _______
- [ ] Crypto-Policy (RHEL 8+): _______
- [ ] FIPS Mode: _______
- [ ] SELinux: _______
- [ ] Service: _______

### Certificate Checks
- [ ] Certificate expiration date
- [ ] Subject/hostname matches
- [ ] SANs present and correct
- [ ] Certificate/key pair match
- [ ] Signature algorithm (SHA-256 or stronger; no SHA-1 or MD5)
- [ ] Key size (>= 2048 bits)

### Trust Chain
- [ ] Certificate validates with system CA bundle
- [ ] Intermediate certificates present
- [ ] Root CA trusted by system

### Service Configuration
- [ ] Correct file paths in config
- [ ] File permissions correct (600 for keys)
- [ ] SELinux contexts correct
- [ ] Service config syntax valid

### System Settings
- [ ] Crypto-policy compatible (RHEL 8+)
- [ ] FIPS requirements met (if applicable)
- [ ] Firewall allows connections
- [ ] No SELinux denials

### Testing
- [ ] Connection test with openssl s_client
- [ ] Connection test with curl
- [ ] Hostname verification passes

### Logs
- [ ] Service logs reviewed
- [ ] System journal checked
- [ ] SELinux audit log checked

27.12 Key Takeaways

  1. Always follow the 7-step methodology - don’t skip steps
  2. Check RHEL version first - behavior varies significantly
  3. Verify basic certificate properties before complex troubleshooting
  4. Trust chain issues are the most common problem
  5. File permissions cause many “mysterious” failures
  6. Crypto-policies (RHEL 8+) affect everything
  7. SELinux can block certificate access
  8. Logs tell the story - always check them

27.13 Practice Scenarios

See upcoming chapters for detailed troubleshooting of:

  • Chapter 28: Common RHEL Certificate Errors
  • Chapter 29: Service-Specific Troubleshooting
  • Chapter 30: certmonger Troubleshooting
  • Chapter 31: Crypto-Policy Troubleshooting
  • Chapter 32: SOS Report Analysis
  • Chapter 33: Emergency Procedures

Quick Reference

┌────────────────────────────────────────────────────────────┐
│ 7-STEP TROUBLESHOOTING METHOD                              │
├────────────────────────────────────────────────────────────┤
│ 1. Identify: RHEL version, OpenSSL, crypto-policy          │
│ 2. Verify: Expiry, hostname, key match, algorithm          │
│ 3. Trust: CA validation, chain, intermediates              │
│ 4. Config: Service files, paths, permissions               │
│ 5. System: Crypto-policy, FIPS, SELinux, firewall          │
│ 6. Test: Live connections, curl, openssl s_client          │
│ 7. Logs: Service logs, journal, SELinux audit              │
└────────────────────────────────────────────────────────────┘

🧪 Hands-On Lab

Lab 15: Troubleshooting Scenarios

Practice diagnosing and fixing an expired certificate problem (one implemented scenario)

  • 📁 Location: labs/en_US/15-troubleshooting-scenarios/
  • ⏱️ Time: 15-20 minutes
  • 🎯 Level: Advanced

Chapter Navigation

Chapter 28: Common RHEL Certificate Errors

Learn from Others’ Pain: This chapter catalogs the most common certificate errors on RHEL, organized by type and version. When you encounter an error, look here first!


28.1 Using This Chapter

How to use this troubleshooting guide:

  1. See an error? Search this chapter for the error message
  2. Service won’t start? Check Section 28.3 (Configuration Errors)
  3. Connection fails? Check Section 28.4 (Validation Errors)
  4. After RHEL upgrade? Check Section 28.7 (Version-Specific)
  5. Not sure? Use the methodology from Chapter 27

28.2 Most Common Errors (Top 10)

Quick Reference Table

#ErrorCommon CauseQuick Fix
1Certificate expiredForgot to renewRenew certificate
2unable to get local issuerMissing CA in trust storeAdd CA to /etc/pki/ca-trust/source/anchors/
3certificate verify failedChain incompleteInstall intermediate certs
4Permission deniedWrong file permissionschmod 600 on key file
5hostname does not matchCN/SAN mismatchReissue with correct SANs
6no shared cipherCipher incompatibilityCheck crypto-policy (RHEL 8+)
7ca md too weakSHA-1 signature (RHEL 9+)Reissue with SHA-256+
8wrong version numberTLS version mismatchCheck client TLS support
9CA_UNREACHABLEcertmonger can’t reach IPACheck IPA connectivity
10SELinux denying accessWrong SELinux contextrestorecon on cert files

28.3 Configuration Errors

Error: “SSLCertificateFile: file does not exist or is empty”

Services: Apache

Symptom:

AH00526: Syntax error on line 100 of /etc/httpd/conf.d/ssl.conf:
SSLCertificateFile: file '/etc/pki/tls/certs/server.crt' does not exist or is empty

Diagnosis:

ls -l /etc/pki/tls/certs/server.crt
# ls: cannot access '/etc/pki/tls/certs/server.crt': No such file or directory

Solutions:

# Solution 1: Fix path in config
sudo vi /etc/httpd/conf.d/ssl.conf
# Correct the SSLCertificateFile path

# Solution 2: Install certificate to expected location
sudo cp server.crt /etc/pki/tls/certs/

# Solution 3: Restore from backup
sudo cp /var/backups/certificates/latest/server.crt /etc/pki/tls/certs/

Error: “Private key does not match this certificate”

Services: Apache, NGINX, Postfix

Symptom:

SSL Library Error: error:0B080074:x509 certificate routines:
X509_check_private_key:key values mismatch

Diagnosis:

# Check if cert and key match
CERT_MOD=$(openssl x509 -noout -modulus -in /etc/pki/tls/certs/server.crt | openssl md5)
KEY_MOD=$(openssl rsa -noout -modulus -in /etc/pki/tls/private/server.key | openssl md5)

echo "Cert: $CERT_MOD"
echo "Key:  $KEY_MOD"
# If different → mismatch!

Cause: Certificate was issued for a different private key

Solution:

# Regenerate CSR with the CORRECT key
openssl req -new -key /etc/pki/tls/private/server.key -out server.csr \
  -subj "/CN=server.example.com"

# Submit CSR to CA, get new certificate
# Install new certificate

Error: “Permission denied” on Private Key

Services: All

Symptom:

Permission denied: Can't open PEM file '/etc/pki/tls/private/server.key'

Diagnosis:

ls -l /etc/pki/tls/private/server.key
# -rw-r--r--. 1 root root  ← Too permissive!

# Check if service user can read it
sudo -u apache cat /etc/pki/tls/private/server.key >/dev/null
# Permission denied

Solution:

# Set correct permissions
sudo chmod 600 /etc/pki/tls/private/server.key
sudo chown apache:apache /etc/pki/tls/private/server.key

# For services that need specific ownership:
# OpenLDAP:
sudo chown ldap:ldap /etc/openldap/certs/ldap.key

# PostgreSQL:
sudo chown postgres:postgres /var/lib/pgsql/data/server.key

# MySQL:
sudo chown mysql:mysql /etc/mysql/certs/server.key

28.4 Validation Errors

Error: “certificate verify failed”

Services: All

Full Error:

SSL_connect: error:14090086:SSL routines:ssl3_get_server_certificate:certificate verify failed

Common Causes:

Cause 1: Self-signed certificate not trusted

# Diagnosis
openssl verify /etc/pki/tls/certs/server.crt
# error 18: self signed certificate

# Solution: Add to trust store
sudo cp server.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

Cause 2: Missing CA certificate

# Diagnosis
openssl verify /etc/pki/tls/certs/server.crt
# error 20: unable to get local issuer certificate

# Solution: Add CA certificate
sudo cp ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

Cause 3: Missing intermediate certificate

# Diagnosis
openssl s_client -connect server.example.com:443 -showcerts
# Verify return code: 21 (unable to verify the first certificate)

# Solution: Include intermediate in cert file
cat server.crt intermediate.crt > /etc/pki/tls/certs/server-chain.crt
# Update service config to use server-chain.crt

Error: “certificate has expired”

Services: All

Symptom:

SSL_connect: error:14090086:SSL routines:ssl3_get_server_certificate:certificate has expired

Diagnosis:

# Check expiration
openssl x509 -in /etc/pki/tls/certs/server.crt -noout -dates
# notAfter=Jan 15 23:59:59 2024 GMT  ← In the past!

Solutions:

# Solution 1: If certmonger tracking
sudo ipa-getcert resubmit -f /etc/pki/tls/certs/server.crt

# Solution 2: Manual renewal
# Generate new CSR, submit to CA, install new cert

# Solution 3: Emergency - temporary self-signed
sudo /usr/local/bin/emergency-self-signed-cert.sh $(hostname -f)
# See Chapter 33 for emergency procedures

Error: “hostname (or IP address) does not match certificate”

Services: All (especially browsers)

Full Error:

SSL: certificate subject name 'server.example.com' does not match target host name 'www.example.com'

Diagnosis:

# Check certificate CN and SANs
openssl x509 -in /etc/pki/tls/certs/server.crt -noout -subject -ext subjectAltName

# Output:
# subject=CN=server.example.com
# X509v3 Subject Alternative Name:
#     DNS:server.example.com
#
# Problem: Accessing www.example.com but cert only has server.example.com

Solution:

# Reissue certificate with correct SANs
openssl req -new -key server.key -out server.csr \
  -subj "/CN=www.example.com" \
  -addext "subjectAltName=DNS:www.example.com,DNS:server.example.com,DNS:example.com"

# Or use wildcard: *.example.com

28.5 Trust Chain Errors

Error: “unable to get local issuer certificate”

Error Code: 20

Symptom:

openssl verify /etc/pki/tls/certs/server.crt
# error 20 at 0 depth lookup: unable to get local issuer certificate

Cause: CA that signed the certificate is not in system trust store

Solution:

# Get CA certificate (from CA or extract from chain)
# Add to trust store
sudo cp issuing-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

# Verify
openssl verify /etc/pki/tls/certs/server.crt
# /etc/pki/tls/certs/server.crt: OK

Error: “unable to verify the first certificate”

Error Code: 21

Symptom:

openssl s_client -connect server.example.com:443
# Verify return code: 21 (unable to verify the first certificate)

Cause: Server sending certificate without intermediate(s)

Diagnosis:

# Count certificates in chain
openssl s_client -connect server.example.com:443 -showcerts 2>&1 | \
  grep -c "BEGIN CERTIFICATE"
# If shows 1: Only server cert (missing intermediate!)
# Should show 2+: Server + intermediate(s)

Solution:

# Create certificate bundle with intermediate
cat server.crt intermediate.crt > /etc/pki/tls/certs/server-bundle.crt

# Update service config
# Apache:
SSLCertificateFile /etc/pki/tls/certs/server-bundle.crt

# Or use SSLCertificateChainFile (Apache):
SSLCertificateChainFile /etc/pki/tls/certs/intermediate.crt

# NGINX:
ssl_certificate /etc/pki/tls/certs/server-bundle.crt;

# Reload service

28.6 Crypto-Policy Errors (RHEL 8/9/10)

Error: “no shared cipher”

Services: All (RHEL 8/9/10)

Symptom:

SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure

Diagnosis:

# Check current policy
update-crypto-policies --show
# DEFAULT

# Test connection showing ciphers
openssl s_client -connect server:443 -cipher 'ALL'

# Check what ciphers are available under current policy
openssl ciphers -v | head -20

Common Causes & Solutions:

Cause 1: Client too old (needs TLS 1.0)

# Temporary test with LEGACY
sudo update-crypto-policies --set LEGACY
sudo systemctl restart httpd

# If works → client compatibility issue
# Proper fix: Update client or create custom policy module

Cause 2: Server crypto-policy too strict

# If using FUTURE policy with old clients
update-crypto-policies --show
# FUTURE

# Temporary: Use DEFAULT
sudo update-crypto-policies --set DEFAULT
sudo systemctl restart services

Error: “SSL routines:tls_post_process_client_hello:no shared cipher”

Services: All (RHEL 9+)

Symptom: Client can’t negotiate cipher with server

Solution:

# RHEL 9: Check if client using very old ciphers
# May need LEGACY policy temporarily

# Check server configuration for overrides
grep -r "SSLCipherSuite\|ssl_ciphers" /etc/httpd/ /etc/nginx/

# If hard-coded ciphers found, remove them
# Let crypto-policy handle it

28.7 RHEL Version-Specific Errors

RHEL 7 Specific Errors

Error: “dh key too small”

SSL routines:ssl3_check_cert_and_algorithm:dh key too small

Cause: Default DH parameters too small for modern clients

Solution:

# Generate stronger DH parameters
openssl dhparam -out /etc/pki/tls/dhparams.pem 2048

# Apache: Add to ssl.conf
SSLOpenSSLConfCmd DHParameters "/etc/pki/tls/dhparams.pem"

# NGINX: Add to config
ssl_dhparam /etc/pki/tls/dhparams.pem;

RHEL 8/9/10 Specific Errors

Error: “ca md too weak” (RHEL 9+)

error 3 at 0 depth lookup: CA md too weak

Cause: Certificate has SHA-1 signature (blocked on RHEL 9+)

Diagnosis:

openssl x509 -in server.crt -noout -text | grep "Signature Algorithm"
# Signature Algorithm: sha1WithRSAEncryption  ← Problem!

Solution:

# Reissue certificate with SHA-256 or better
# No workaround - SHA-1 is blocked for security

# Request new certificate
openssl req -new -key server.key -out server.csr -sha256 \
  -subj "/CN=server.example.com"

Error: “Provider ‘legacy’ could not be loaded” (RHEL 9+)

openssl: error while loading shared libraries: Provider 'legacy' could not be loaded

Cause: Trying to use legacy algorithm without provider

Solution:

# Use legacy provider explicitly
openssl md5 -provider legacy file.txt

# Or update to use modern algorithm
openssl sha256 file.txt

28.8 SELinux Errors

Error: SELinux Preventing Access to Certificate

Symptom:

audit: type=1400 audit(timestamp): avc: denied { read } for pid=1234 comm="httpd"
name="server.key" dev="sda1" ino=12345 scontext=system_u:system_r:httpd_t:s0
tcontext=unconfined_u:object_r:admin_home_t:s0 tclass=file permissive=0

Diagnosis:

# Check for AVC denials
sudo ausearch -m avc -ts recent | grep cert

# Check SELinux context
ls -Z /etc/pki/tls/certs/server.crt
ls -Z /etc/pki/tls/private/server.key

Solution:

# Fix SELinux context
sudo restorecon -Rv /etc/pki/tls/

# Verify
ls -Z /etc/pki/tls/certs/server.crt
# system_u:object_r:cert_t:s0  ← Correct

# If still issues, check if SELinux is blocking
sudo ausearch -m avc -ts recent

# Generate policy if needed
sudo ausearch -m avc -ts recent | audit2allow -M mycert
sudo semodule -i mycert.pp

28.9 certmonger Errors

Error: CA_UNREACHABLE

Symptom:

sudo getcert list
# status: CA_UNREACHABLE

Diagnosis:

# Check IPA connectivity
ipa ping

# Check Kerberos ticket
klist

# Check IPA services
ssh ipa-server "sudo ipactl status"

Solutions:

# Solution 1: Renew Kerberos ticket
kinit -k host/$(hostname -f)@REALM

# Solution 2: Check network connectivity
ping ipa.example.com

# Solution 3: Restart certmonger
sudo systemctl restart certmonger

# Solution 4: Resubmit request
sudo ipa-getcert resubmit -f /etc/pki/tls/certs/server.crt

Error: CA_REJECTED

Symptom:

sudo getcert list
# status: CA_REJECTED
# ca-error: Server unwilling to issue certificate

Common Causes:

Cause 1: Service principal doesn’t exist

# Check if principal exists
ipa service-show HTTP/$(hostname -f)

# If not found, add it
ipa service-add HTTP/$(hostname -f)

# Resubmit
sudo ipa-getcert resubmit -f /etc/pki/tls/certs/server.crt

Cause 2: Host not enrolled to IPA

# Check enrollment
ipa host-show $(hostname -f)

# If not enrolled
sudo ipa-client-install

28.10 GPG/PGP Legacy Key Errors

Error: “skipped PGP-2 keys” on Import

Tool: GnuPG (gpg)

Symptom:

$ gpg --allow-old-cipher-algos --import keyfile.asc
gpg: Total number processed: 2
gpg:     skipped PGP-2 keys: 2

The key is silently rejected — even with --allow-old-cipher-algos.

Cause: The key file contains OpenPGP version 3 keys (PGP 2.x era). Modern GnuPG (2.2+) refuses to import v3 key packets entirely. These keys typically use RSA with MD5 signatures, both of which are cryptographically obsolete.

Diagnosis — Inspect the key packets:

gpg --list-packets keyfile.asc

Example output:

# off=0 ctb=95 tag=5 hlen=3 plen=930
:key packet: [obsolete version 3]
# off=933 ctb=b4 tag=13 hlen=2 plen=27
:user ID packet: "User Name <user@example.org>"
# off=962 ctb=89 tag=2 hlen=3 plen=277
:signature packet: algo 1, keyid A1B2C3D4E5F60789
        version 3, created 1034280585, md5len 5, sigclass 0x10
        digest algo 1, begin of digest a1 26
        data: [2047 bits]

The critical indicators are:

  • :key packet: [obsolete version 3] — v3 format, rejected by modern GnuPG
  • algo 1 — RSA (encrypt or sign)
  • digest algo 1 — MD5 (cryptographically broken)
  • version 3 on the signature — old signature format
  • sigclass 0x10 — generic certification of a User ID

Solution: There is no way to “upgrade” a v3 key. You must generate a new modern key and retire the old one:

# 1. Generate a modern key (Ed25519 + Curve25519)
gpg --full-generate-key
#    Choose: (9) ECC (sign and encrypt)
#    Curve:  ed25519 (signing), cv25519 (encryption)

# 2. Verify the new key
gpg --list-keys --keyid-format long

# 3. If you still control the old key, cross-sign for trust continuity
gpg --default-key OLDKEYID --sign-key NEWKEYID

# 4. Generate a revocation certificate for the old key
gpg --output revoke-old.asc --gen-revoke OLDKEYID

# 5. Publish the new key
gpg --send-keys NEWKEYID

# 6. Revoke the old key
gpg --import revoke-old.asc
gpg --send-keys OLDKEYID

After migration, update all systems that reference the old key (CI/CD, package signing, email encryption).


Understanding gpg --list-packets Output

When debugging GPG key issues, gpg --list-packets shows the raw OpenPGP packet structure. Here is a complete reference for interpreting the output.

Packet Header Fields

Each packet line starts with:

# off=0 ctb=95 tag=5 hlen=3 plen=930
FieldMeaning
offByte offset in the file (start position of this packet)
ctbCipher Type Byte (raw header byte in hex, encodes format + tag)
tagPacket type (decoded — see table below)
hlenHeader length in bytes
plenPayload length in bytes

Packet Tags (tag=)

TagPacket Type
1Public-Key Encrypted Session Key
2Signature
3Symmetric-Key Encrypted Session Key
4One-Pass Signature
5Public Key
6Secret Key
7Secret Subkey
8Compressed Data
9Symmetrically Encrypted Data
10Marker
11Literal Data
12Trust
13User ID
14Public Subkey
17User Attribute
18Encrypted + Integrity Protected Data
19Modification Detection Code

Public-Key Algorithm IDs (algo)

IDAlgorithm
1RSA (encrypt or sign)
2RSA (encrypt-only)
3RSA (sign-only)
16Elgamal (encrypt-only)
17DSA
18ECDH
19ECDSA
21Diffie-Hellman
22EdDSA (Ed25519, etc.)

Digest (Hash) Algorithm IDs (digest algo)

IDAlgorithmStatus
1MD5Broken — do not use
2SHA-1Deprecated — blocked on RHEL 9+
3RIPEMD-160Legacy
8SHA-256Recommended
9SHA-384Strong
10SHA-512Strong
11SHA-224Acceptable

Signature Classes (sigclass)

CodeMeaning
0x00Binary document signature
0x01Canonical text signature
0x02Standalone signature
0x10Generic key certification
0x11Persona certification
0x12Casual certification
0x13Positive certification
0x18Subkey binding
0x19Primary key binding
0x1FDirect key signature
0x20Key revocation
0x28Subkey revocation
0x30Certification revocation
0x40Timestamp
0x50Third-party confirmation

Key Packet Versions

VersionEraStatus
3PGP 2.x (1990s)Obsolete — uses MD5 internally, rejected by modern GnuPG
4OpenPGP RFC 4880 (2007)Current standard
5Draft (crypto-refresh)Emerging

Signature Packet Fields

For a signature like:

:signature packet: algo 1, keyid A1B2C3D4E5F60789
        version 3, created 1034280585, md5len 5, sigclass 0x10
        digest algo 1, begin of digest a1 26
        data: [2047 bits]
FieldMeaning
algo 1Public-key algorithm used (RSA)
keyidShort identifier of the signing key
version 3Signature format version (v3 = legacy)
createdUnix timestamp of signature creation
md5lenLength of MD5 prefix (v3 legacy artifact)
sigclass 0x10Signature type (generic key certification)
digest algo 1Hash algorithm (MD5)
begin of digestFirst 2 bytes of hash (for quick verification checks)
data: [2047 bits]RSA signature data (~2048-bit key)

28.11 RPM Signature Errors After RHEL Upgrade

Error: “Certificate invalid: policy violation — SHA1 is not considered secure”

Tools: RPM, DNF

Symptom:

After upgrading to RHEL 9+ or RHEL 10, RPM commands produce signature verification errors for third-party packages:

$ rpm -qa
error: Verifying a signature using certificate
  D4E7A923F10B82C6459831AE5F6C0D9BA47E31D2
  (Third-Party Vendor (Release signing) <security@vendor.example.com>):
  1. Certificate 5F6C0D9BA47E31D2 invalid: policy violation
      because: No binding signature at time 2024-08-12T10:44:42Z
      because: Policy rejected non-revocation signature
               (PositiveCertification) requiring second pre-image resistance
      because: SHA1 is not considered secure
  2. Certificate 5F6C0D9BA47E31D2 invalid: policy violation
      because: No binding signature at time 2026-04-16T19:46:38Z
      because: Policy rejected non-revocation signature
               (PositiveCertification) requiring second pre-image resistance
      because: SHA1 is not considered secure

RPM commands still function but produce excessive error output for every package signed with the affected key.

Cause: Third-party GPG signing keys that use SHA-1 for their binding signatures (self-certifications) are rejected by RHEL 9+ crypto-policies (SHA-1 blocked by default) and RHEL 10 (SHA-1 support removed entirely). Packages installed before the upgrade retain their old SHA-1 signatures in the RPM database.

Diagnosis:

# List all imported GPG keys in the RPM database
rpm -q gpg-pubkey --qf '%{NAME}-%{VERSION}-%{RELEASE}\t%{SUMMARY}\n'

Solution:

# 1. Remove the old third-party signing key
#    The certificate ID 5F6C0D9BA47E31D2 maps to version a47e31d2 in RPM
rpm -e --allmatches gpg-pubkey-a47e31d2-6142699d

# 2. Import the vendor's updated key
rpm --import https://vendor.example.com/keys/signing.asc

# 3. Verify errors are gone
rpm -qa > /dev/null

Key ID mapping: The certificate ID in the error (e.g., 5F6C0D9BA47E31D2) corresponds to the RPM gpg-pubkey version in lowercase hex (a47e31d2). If rpm -e reports “not installed”, list all keys with rpm -q gpg-pubkey --qf '...' to find the exact version-release string.


28.12 RPM Database Corruption After RHEL Upgrade

Error: “Malformed MPI” / “non-conformant OpenPGP implementation”

Tools: RPM

Symptom:

After upgrading to RHEL 9+ or RHEL 10, RPM reports a corrupt signature on every database query:

error: rpmdbNextIterator: skipping h#       9
Header RSA signature: BAD (header tag 268: invalid OpenPGP signature:
  Parsing an OpenPGP packet:
  Failed to parse Signature Packet
      because: Signature appears to be created by a non-conformant
               OpenPGP implementation, see
               <https://github.com/rpm-software-management/rpm/issues/2351>.
      because: Malformed MPI: leading bit is not set: expected bit 8 to
               be set in   110010 (32))
Header SHA256 digest: OK
Header SHA1 digest: OK

Cause: Some third-party packages were signed using non-standard OpenPGP implementations that produce malformed MPI (Multi-Precision Integer) values in RSA signatures. The older RPM in RHEL 7/8 tolerated these, but the Sequoia-PGP based parser in RHEL 9+/10 rejects them.

Diagnosis:

# Identify the broken package using the header number from the error (h# 9)
rpm -q --nosignature --querybynumber 9

Solution:

# 1. Backup the RPM database
tar zcvf /var/preserve/rpmdb-$(date +"%d%m%Y").tar.gz /usr/lib/sysimage/rpm/
# Note: on RHEL 8/9, the database is in /var/lib/rpm/

# 2. Identify the damaged package
rpm -q --nosignature --querybynumber <NUMBER_FROM_ERROR>

# 3. Remove the package with the broken signature
rpm -e --nosignature --nodigest <package-name>
# If removal fails, remove just the database entry:
rpm -e --justdb --nodeps <package-name>

# 4. Rebuild the RPM database
rpm --rebuilddb

# 5. Verify errors are resolved
rpm -qa > /dev/null

# 6. Reinstall from an updated repository
dnf install <package-name>

Note: On RHEL 10, the RPM database is in /usr/lib/sysimage/rpm/. On RHEL 8/9, it’s in /var/lib/rpm/.

Reference: RPM issue #2351 documents the stricter MPI parsing introduced with the Sequoia-PGP backend.


Combined Scenario: Both Errors After Upgrade

When upgrading from RHEL 7/8 to RHEL 9+/10, both errors often appear simultaneously. Resolve them in this order:

  1. Remove old SHA-1 signing keys and import updated vendor keys
  2. Rebuild the RPM database with rpm --rebuilddb
  3. If malformed MPI errors persist, identify affected packages by header number (rpm -q --nosignature --querybynumber <N>), remove them, and reinstall from updated repositories

28.13 Browser/Client Errors

Error: “NET::ERR_CERT_COMMON_NAME_INVALID”

Symptom: Browser shows “Your connection is not private”

Cause: Hostname doesn’t match certificate CN or SANs

Diagnosis:

# Check what you're accessing
echo "Accessing: www.example.com"

# Check certificate SANs
openssl s_client -connect www.example.com:443 2>&1 | \
  openssl x509 -noout -ext subjectAltName
# X509v3 Subject Alternative Name:
#     DNS:server.example.com  ← Doesn't include www.example.com!

Solution:

# Reissue certificate with correct SANs
openssl req -new -key server.key -out server.csr \
  -subj "/CN=www.example.com" \
  -addext "subjectAltName=DNS:www.example.com,DNS:server.example.com,DNS:example.com"

Error: “NET::ERR_CERT_AUTHORITY_INVALID”

Symptom: Browser doesn’t trust certificate

Cause: CA not in browser’s trust store (self-signed or internal CA)

For Internal CA:

# Distribute CA certificate to clients
# Users need to install CA in their browser

# Or add to system trust (Linux clients)
sudo cp corporate-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

For Self-Signed (Testing Only):

# Don't use self-signed in production!
# Get proper certificate from CA

28.14 Firewall/Network Errors

Error: Connection Timeout

Symptom: Can’t connect to HTTPS port

Diagnosis:

# Check if service listening
ss -tlnp | grep :443

# Check firewall
sudo firewall-cmd --list-services | grep https

# Test locally
curl -vk https://localhost/

# Test remotely
telnet server.example.com 443

Solution:

# Open firewall
sudo firewall-cmd --add-service=https --permanent
sudo firewall-cmd --reload

# Verify
sudo firewall-cmd --list-all

28.15 Error Message Dictionary

Quick Lookup Table

Error MessageError CodeCauseChapter
“certificate has expired”-Expired cert28.4
“unable to get local issuer”20Missing CA28.4
“unable to verify first cert”21Missing intermediate28.4
“self signed certificate”18Self-signed not trusted28.4
“certificate verify failed”-General validation28.4
“ca md too weak”3SHA-1 signature28.7
“no shared cipher”-Cipher mismatch28.6
“wrong version number”-TLS version mismatch28.6
“Permission denied”-File permissions28.3
“key values mismatch”-Cert/key don’t pair28.3
“hostname does not match”-CN/SAN mismatch28.4
“CA_UNREACHABLE”-certmonger can’t reach IPA28.9
“CA_REJECTED”-IPA rejected request28.9
“skipped PGP-2 keys”-GPG v3 key import rejected28.10
“SHA1 is not considered secure”-Third-party GPG key uses SHA-128.11
“Malformed MPI”-Non-conformant OpenPGP signature28.12

28.16 Quick Diagnostic Commands

Universal Troubleshooting Commands

#============================================#
# RUN THESE FOR ANY CERTIFICATE ERROR
#============================================#

# 1. Check RHEL version
cat /etc/redhat-release

# 2. Check certificate file
openssl x509 -in /path/to/cert.crt -noout -text

# 3. Check expiration
openssl x509 -in /path/to/cert.crt -noout -dates

# 4. Check trust
openssl verify /path/to/cert.crt

# 5. Check permissions
ls -lZ /path/to/cert.crt
ls -lZ /path/to/key.key

# 6. Check cert/key match
openssl x509 -noout -modulus -in cert.crt | openssl md5
openssl rsa -noout -modulus -in key.key | openssl md5

# 7. Test connection
openssl s_client -connect server:443

# 8. Check logs
sudo journalctl -xe | grep -i cert
sudo tail -f /var/log/httpd/ssl_error_log

# 9. Check SELinux
sudo ausearch -m avc -ts recent | grep cert

# 10. Check crypto-policy (RHEL 8+)
update-crypto-policies --show

28.17 Error Resolution Flowchart

Certificate Error Occurred
    │
    ├─ Service won't start?
    │   ├─ Check config syntax
    │   ├─ Check file paths
    │   ├─ Check permissions (600 for keys)
    │   └─ Check SELinux context
    │
    ├─ Connection fails?
    │   ├─ Check firewall
    │   ├─ Check service listening
    │   ├─ Test with openssl s_client
    │   └─ Check network routing
    │
    ├─ Certificate validation error?
    │   ├─ Check expiration
    │   ├─ Check trust chain
    │   ├─ Check hostname match
    │   └─ Check intermediate certs
    │
    ├─ Cipher/protocol error?
    │   ├─ Check crypto-policy (RHEL 8+)
    │   ├─ Check TLS versions
    │   └─ Test with different TLS version
    │
    └─ certmonger error?
        ├─ CA_UNREACHABLE → Check IPA connectivity
        ├─ CA_REJECTED → Check principal exists
        └─ See Chapter 30

28.18 Key Takeaways

  1. Most errors are predictable - Common patterns
  2. Always check expiration first - #1 cause of issues
  3. Permissions matter - 600 for keys, 644 for certs
  4. Trust chain critical - Missing CA or intermediate
  5. RHEL version matters - Different errors per version
  6. crypto-policies affect everything (RHEL 8+)
  7. SELinux can block - Check contexts
  8. Reference Chapter 27 for systematic approach

Quick Reference Card

┌───────────────────────────────────────────────────────────────────┐
│ COMMON CERTIFICATE ERRORS QUICK REFERENCE                         │
├───────────────────────────────────────────────────────────────────┤
│ Expired:           Check: openssl x509 -noout -dates              │
│                    Fix: Renew certificate                         │
│                                                                   │
│ Trust:             Check: openssl verify cert.crt                 │
│                    Fix: Add CA to /etc/pki/ca-trust/...           │
│                                                                   │
│ Hostname:          Check: openssl x509 -noout -ext subjectAltName │
│                    Fix: Reissue with correct SANs                 │
│                                                                   │
│ Permissions:       Check: ls -lZ cert.crt key.key                 │
│                    Fix: chmod 600 key.key                         │
│                                                                   │
│ Cert/Key match:    Check: Compare MD5 of modulus                  │
│                    Fix: Regenerate CSR with correct key           │
│                                                                   │
│ No shared cipher:  Check: update-crypto-policies --show           │
│                    Fix: Update policy or client                   │
│                                                                   │
│ SELinux:           Check: ausearch -m avc | grep cert             │
│                    Fix: restorecon -Rv /etc/pki/tls/              │
└───────────────────────────────────────────────────────────────────┘

Always start with: Chapter 27 (7-step methodology)

Chapter Navigation

Chapter 29: Service-Specific Troubleshooting

Service by Service: Each RHEL service has unique certificate requirements and failure modes. This chapter provides targeted troubleshooting for each major service.


29.1 Apache httpd Troubleshooting

Apache Won’t Start

Diagnostic Steps:

#============================================#
# APACHE CERTIFICATE TROUBLESHOOTING
#============================================#

# Step 1: Check Apache status
systemctl status httpd
sudo journalctl -xe -u httpd

# Step 2: Test configuration
sudo apachectl configtest
# Look for SSL-related errors

# Step 3: Check if mod_ssl loaded
sudo httpd -M | grep ssl
# Should show: ssl_module (shared)

# Step 4: Verify certificate files
ls -l /etc/pki/tls/certs/*.crt
ls -l /etc/pki/tls/private/*.key

# Step 5: Check permissions
ls -l /etc/pki/tls/private/server.key
# Should be: -rw------- (600)

# Step 6: Verify cert/key match
CERT_MOD=$(openssl x509 -noout -modulus -in /etc/pki/tls/certs/server.crt | openssl md5)
KEY_MOD=$(openssl rsa -noout -modulus -in /etc/pki/tls/private/server.key | openssl md5)
[ "$CERT_MOD" = "$KEY_MOD" ] && echo "✅ Match" || echo "❌ Mismatch!"

# Step 7: Check SELinux
sudo ausearch -m avc -ts recent | grep httpd | grep cert

Common Apache SSL Errors

ErrorCauseSolution
“SSLCertificateFile: file does not exist”Wrong pathFix path in ssl.conf
“key values mismatch”Cert/key don’t pairRegenerate with correct key
“unable to load certificate”File format issueEnsure PEM format
“Syntax error” in ssl.confConfig typoRun apachectl configtest
“unable to verify certificate”Chain issueAdd intermediate cert

29.2 NGINX Troubleshooting

NGINX SSL/TLS Issues

Diagnostic Steps:

#============================================#
# NGINX CERTIFICATE TROUBLESHOOTING
#============================================#

# Step 1: Test configuration
sudo nginx -t

# Step 2: Show full config
sudo nginx -T | grep ssl_certificate

# Step 3: Check certificate files
ls -l /etc/pki/tls/certs/nginx.crt
ls -l /etc/pki/tls/private/nginx.key

# Step 4: Verify cert/key pair
openssl x509 -noout -modulus -in /etc/pki/tls/certs/nginx.crt | openssl md5
openssl rsa -noout -modulus -in /etc/pki/tls/private/nginx.key | openssl md5

# Step 5: Check NGINX error log
sudo tail -50 /var/log/nginx/error.log | grep -i ssl

# Step 6: Check if NGINX running
systemctl status nginx
ss -tlnp | grep nginx

Common NGINX SSL Errors

ErrorCauseSolution
“SSL: error:0200100D”Permission denied on keychmod 600 on key
“no "ssl" is defined”Missing ssl in listenAdd listen 443 ssl;
“cannot load certificate”File not foundCheck path
“PEM_read_bio:no start line”Wrong formatEnsure PEM format
“nginx: [emerg] bind() failed”Port in useCheck what’s on port 443

29.3 Postfix Troubleshooting

Postfix TLS Issues

Diagnostic Steps:

#============================================#
# POSTFIX TLS TROUBLESHOOTING
#============================================#

# Step 1: Check Postfix TLS config
sudo postconf | grep -i tls

# Step 2: View specific settings
sudo postconf smtpd_tls_cert_file smtpd_tls_key_file

# Step 3: Test configuration
sudo postfix check

# Step 4: Check certificate files
ls -l $(sudo postconf -h smtpd_tls_cert_file)
ls -l $(sudo postconf -h smtpd_tls_key_file)

# Step 5: Test SMTP TLS
openssl s_client -starttls smtp -connect localhost:25

# Step 6: Check mail logs
sudo tail -f /var/log/maillog | grep -i tls

# Step 7: Check if STARTTLS offered
telnet localhost 25
# Type: EHLO test
# Should show: 250-STARTTLS

Common Postfix TLS Errors

ErrorCauseSolution
“SSL_accept error”Cert/key issueCheck cert/key pair
“TLS is required but not available”TLS not enabledSet security_level = may
“no shared cipher”Cipher mismatchCheck crypto-policy
“certificate verify failed”Chain issueInstall intermediate
“Permission denied”Key permissionschmod 600 on key

29.4 OpenLDAP Troubleshooting

LDAPS Issues

Diagnostic Steps:

#============================================#
# OPENLDAP TLS TROUBLESHOOTING
#============================================#

# Step 1: Check if slapd listening on 636
ss -tlnp | grep 636

# Step 2: Check TLS configuration
sudo slapcat -b "cn=config" | grep -i tls

# Step 3: Verify certificate files
ls -l /etc/openldap/certs/ldap.{crt,key}

# Step 4: Check ownership
# CRITICAL: Must be owned by ldap user!
ls -l /etc/openldap/certs/
# Should show: ldap:ldap

# Step 5: Test LDAPS connection
openssl s_client -connect localhost:636

# Step 6: Test with ldapsearch
ldapsearch -H ldaps://localhost:636 -x -b "" -s base

# Step 7: Check slapd logs
sudo journalctl -u slapd | grep -i tls

Common OpenLDAP TLS Errors

ErrorCauseSolution
“TLS: can’t accept”Key not readablechown ldap:ldap on key
“TLS: hostname does not match”CN/SAN mismatchReissue with correct hostname
“certificate verify failed”CA not trustedAdd CA to trust store
“Permission denied”Wrong ownershipchown ldap:ldap
“TLS engine not initialized”TLS not configuredAdd TLS directives

29.5 PostgreSQL Troubleshooting

PostgreSQL SSL Issues

Diagnostic Steps:

#============================================#
# POSTGRESQL SSL TROUBLESHOOTING
#============================================#

# Step 1: Check if SSL enabled
sudo -u postgres psql -c "SHOW ssl;"

# Step 2: View SSL settings
sudo -u postgres psql -c "SHOW ssl_cert_file; SHOW ssl_key_file;"

# Step 3: Check certificate files
ls -l /var/lib/pgsql/data/server.{crt,key}

# Step 4: Check ownership
# Must be owned by postgres user
ls -l /var/lib/pgsql/data/server.key
# -rw------- postgres postgres

# Step 5: Test SSL connection
psql "host=localhost sslmode=require"

# Step 6: Check PostgreSQL logs
sudo tail -f /var/lib/pgsql/data/log/postgresql-*.log | grep -i ssl

# Step 7: Check permissions
sudo -u postgres stat /var/lib/pgsql/data/server.key

Common PostgreSQL SSL Errors

ErrorCauseSolution
“could not load server certificate”Permission deniedchown postgres:postgres, chmod 600
“private key file has wrong permissions”Too permissivechmod 600 on key
“SSL connection has been closed unexpectedly”Trust issueCheck client CA trust
“SSL is not enabled”SSL off in configSet ssl = on

29.6 MySQL/MariaDB Troubleshooting

Database SSL Issues

Diagnostic Steps:

#============================================#
# MYSQL/MARIADB SSL TROUBLESHOOTING
#============================================#

# Step 1: Check if SSL available
mysql -u root -p -e "SHOW VARIABLES LIKE 'have_ssl';"
# Should show: YES

# Step 2: View SSL variables
mysql -u root -p -e "SHOW VARIABLES LIKE '%ssl%';"

# Step 3: Check certificate files
ls -l /etc/mysql/certs/{ca,server}.{crt,key}

# Step 4: Check ownership
# Must be readable by mysql user
ls -l /etc/mysql/certs/
# mysql:mysql

# Step 5: Test SSL connection
mysql --ssl-mode=REQUIRED -h localhost -u root -p

# Step 6: Check connection status
mysql -u root -p -e "STATUS" | grep SSL

# Step 7: Check error log
sudo tail -f /var/log/mariadb/mariadb.log | grep -i ssl

29.7 Cross-Service Issues

Certificate Works in One Service, Fails in Another

Scenario: Same certificate works in Apache but fails in Postfix

Diagnosis:

# Apache works
curl -v https://localhost/
# ✅ OK

# Postfix fails
openssl s_client -starttls smtp -connect localhost:25
# ❌ Error

# Why? Different requirements!

Common Causes:

Cause 1: File ownership

  • Apache: Runs as root (can read root-owned keys)
  • Postfix: Runs as postfix (needs readable key)
  • OpenLDAP: Runs as ldap (needs ldap-owned key)

Cause 2: File locations

  • Apache: /etc/pki/tls/
  • PostgreSQL: /var/lib/pgsql/data/
  • OpenLDAP: /etc/openldap/certs/

Cause 3: Format requirements

  • Most services: Separate cert and key files
  • HAProxy: Combined PEM file
  • Cockpit: Combined cert+key

29.8 Troubleshooting Toolkit

Service-Specific Test Commands

#============================================#
# TEST EACH SERVICE
#============================================#

# Apache HTTPS
curl -v https://localhost/
openssl s_client -connect localhost:443

# NGINX HTTPS
curl -v https://localhost:8443/  # If custom port
openssl s_client -connect localhost:443

# Postfix SMTP
openssl s_client -starttls smtp -connect localhost:25
openssl s_client -connect localhost:465  # SMTPS

# Dovecot IMAP
openssl s_client -connect localhost:993  # IMAPS
openssl s_client -connect localhost:995  # POP3S

# OpenLDAP
openssl s_client -connect localhost:636  # LDAPS
ldapsearch -H ldaps://localhost:636 -x -b ""

# PostgreSQL
psql "host=localhost sslmode=require"

# MySQL/MariaDB
mysql --ssl-mode=REQUIRED -h localhost -u root -p

# Cockpit
openssl s_client -connect localhost:9090

29.9 Key Takeaways

  1. Each service has unique requirements - Ownership, location, format
  2. Always check service-specific logs first
  3. Test with service-specific commands (not just openssl)
  4. Permissions critical - Different users for different services
  5. File locations matter - Service-dependent paths
  6. Configuration syntax varies by service
  7. Reference service chapters (Ch 14-21) for detailed config

Quick Reference Card

┌─────────────────────────────────────────────────────────────┐
│ SERVICE-SPECIFIC TROUBLESHOOTING                            │
├─────────────────────────────────────────────────────────────┤
│ Apache:      apachectl configtest                           │
│              tail -f /var/log/httpd/ssl_error_log           │
│                                                             │
│ NGINX:       nginx -t                                       │
│              tail -f /var/log/nginx/error.log               │
│                                                             │
│ Postfix:     postfix check                                  │
│              tail -f /var/log/maillog | grep TLS            │
│                                                             │
│ OpenLDAP:    slapcat -b "cn=config" | grep TLS              │
│              journalctl -u slapd | grep TLS                 │
│              chown ldap:ldap (CRITICAL!)                    │
│                                                             │
│ PostgreSQL:  psql -c "SHOW ssl;"                            │
│              chown postgres:postgres (CRITICAL!)            │
│                                                             │
│ MySQL:       mysql -e "SHOW VARIABLES LIKE '%ssl%';"        │
│              chown mysql:mysql (CRITICAL!)                  │
└─────────────────────────────────────────────────────────────┘

⚠️ File ownership is service-specific!
✅ Always check logs for each service

Chapter Navigation

Chapter 30: certmonger Troubleshooting

Automation Issues: certmonger is RHEL’s certificate automation tool. When it fails, certificates don’t renew. This chapter teaches you to diagnose and fix certmonger problems quickly.


30.1 certmonger Status Values

Understanding Status Messages

StatusMeaningAction Required
MONITORING✅ All good - cert issued, tracking expiryNone
SUBMITTING🔄 Requesting cert from CAWait (usually seconds)
CA_UNREACHABLE❌ Can’t contact CA serverFix connectivity
CA_REJECTED❌ CA refused requestFix principal/permissions
NEED_KEY_GEN_PIN⏸️ Waiting for PIN (HSM)Provide PIN
NEED_GUIDANCE⚠️ Needs manual interventionCheck request details
PRE_SAVE_COMMAND🔄 Running pre-save scriptWait
POST_SAVE_COMMAND🔄 Running post-save scriptWait
NEWLY_ADDED🆕 Just added, not yet processedWait

30.2 CA_UNREACHABLE Troubleshooting

Most Common certmonger Issue!

Symptom:

sudo getcert list
# status: CA_UNREACHABLE

Diagnosis Steps

#============================================#
# DIAGNOSE CA_UNREACHABLE
#============================================#

# Step 1: Which CA are we trying to reach?
sudo getcert list -v | grep "CA:"
# CA: IPA

# Step 2: Can we reach IPA?
ipa ping
# Pong!  ← Good
# ipa: ERROR: cannot connect to 'https://ipa.example.com/ipa/xml'  ← Bad!

# Step 3: Check Kerberos ticket
klist
# Ticket cache: FILE:/tmp/krb5cc_0
# Valid starting     Expires            Service principal
# ...

# Step 4: Check if ticket expired
klist | grep "host/"
# If no host ticket or expired → Problem!

# Step 5: Check IPA server status
ssh ipa.example.com "sudo ipactl status"

# Step 6: Check network
ping ipa.example.com
curl -k https://ipa.example.com/ipa/config/ca.crt

# Step 7: Check DNS
nslookup ipa.example.com

Solutions for CA_UNREACHABLE

Solution 1: Renew Kerberos Ticket

# Get new host ticket
sudo kinit -k host/$(hostname -f)@REALM

# Verify
klist

# Retry cert request
sudo ipa-getcert resubmit -f /etc/pki/tls/certs/web.crt

Solution 2: Check IPA Server

# On IPA server
sudo ipactl status

# If services down
sudo ipactl restart

# Check specific service
sudo systemctl status pki-tomcatd@pki-tomcat  # CA service

Solution 3: Network/Firewall

# Test IPA connectivity
curl -vk https://ipa.example.com/ipa/xml

# Check firewall on IPA server
ssh ipa.example.com "sudo firewall-cmd --list-services | grep https"

# Check routes
traceroute ipa.example.com

Solution 4: Restart certmonger

sudo systemctl restart certmonger

# Wait a moment
sleep 10

# Check status
sudo getcert list

30.3 CA_REJECTED Troubleshooting

When CA Refuses the Request

Symptom:

sudo getcert list -v
# status: CA_REJECTED
# ca-error: Server at https://ipa.example.com/ipa/xml unwilling to issue certificate

Diagnosis Steps

#============================================#
# DIAGNOSE CA_REJECTED
#============================================#

# Step 1: Check error details
sudo getcert list -v -f /etc/pki/tls/certs/web.crt
# Look at 'ca-error' field

# Step 2: Does service principal exist?
ipa service-show HTTP/$(hostname -f)
# If error: Service not found

# Step 3: Is host enrolled?
ipa host-show $(hostname -f)

# Step 4: Check certificate profile exists
sudo getcert list -v | grep "profile:"
ipa certprofile-show caIPAserviceCert

# Step 5: Check request details
sudo getcert list -v | grep -A30 "Request ID"

Solutions for CA_REJECTED

Solution 1: Create Service Principal

# Add missing service principal
ipa service-add HTTP/$(hostname -f)

# Add SAN (if needed)
ipa service-mod HTTP/$(hostname -f) --addattr=cn=web.example.com

# Retry
sudo ipa-getcert resubmit -f /etc/pki/tls/certs/web.crt

Solution 2: Fix Host Entry

# Re-enroll to IPA if needed
sudo ipa-client-install --force-join

# Verify
ipa host-show $(hostname -f)

Solution 3: Check Permissions

# Check if you have permission to request certs
ipa permission-find --name="Request Certificate"

# Check ACLs
ipa aci-find --name="*cert*"

# May need IPA admin to grant permissions

30.4 Renewal Failures

Certificate Not Renewing

Symptom: Certificate approaching expiry but not renewing

Diagnosis:

#============================================#
# DIAGNOSE RENEWAL FAILURE
#============================================#

# Step 1: Check current status
sudo getcert list -f /etc/pki/tls/certs/web.crt

# Step 2: When should it renew?
# certmonger renews at 2/3 of cert lifetime
# 365-day cert → renews at day 243 (122 days before expiry)

# Step 3: Check certmonger logs
sudo journalctl -u certmonger --since "7 days ago" | grep -i renew

# Step 4: Force renewal attempt
sudo ipa-getcert resubmit -f /etc/pki/tls/certs/web.crt

# Step 5: Watch logs in real-time
sudo journalctl -u certmonger -f

Common Renewal Issues

Issue 1: Post-save command fails

# Check post-save command
sudo getcert list -f /etc/pki/tls/certs/web.crt | grep "post-save"
# post-save command: systemctl reload httpd

# Test command manually
sudo systemctl reload httpd
# If fails → fix the command

# Update command (re-create tracking entry; do not use getcert rekey)
sudo getcert stop-tracking -f /etc/pki/tls/certs/web.crt
sudo getcert start-tracking \
  -f /etc/pki/tls/certs/web.crt \
  -k /etc/pki/tls/private/web.key \
  -C "systemctl reload httpd"

Issue 2: IPA server down during renewal window

# certmonger will retry
# Check retry schedule in logs
sudo journalctl -u certmonger | grep "will try again"

# Manual retry
sudo ipa-getcert resubmit -f /etc/pki/tls/certs/web.crt

30.5 Tracking Issues

Certificate Not Being Tracked

Symptom: Certificate expires because certmonger wasn’t tracking it

Solution:

#============================================#
# START TRACKING EXISTING CERTIFICATE
#============================================#

sudo getcert start-tracking \
  -f /etc/pki/tls/certs/existing.crt \
  -k /etc/pki/tls/private/existing.key \
  -c IPA \
  -K HTTP/$(hostname -f)@REALM

Duplicate Tracking

Symptom: Same certificate tracked multiple times

Diagnosis:

# List all tracked certs
sudo getcert list | grep -E "(Request ID|certificate:)" | \
  awk -F"'" '/certificate:/{cert=$2} /Request ID/{print cert, $2}'

# Look for duplicates

Solution:

# Remove duplicate tracking
sudo getcert stop-tracking -i <duplicate-request-id>

# Keep only one tracking entry per certificate

30.6 Configuration Issues

Wrong CA Configured

Symptom: certmonger trying to reach wrong CA

Diagnosis:

# Check configured CA
sudo getcert list -v | grep "CA:"

# List available CAs
sudo getcert list-cas

Solution:

# Stop tracking with wrong CA
sudo getcert stop-tracking -f /etc/pki/tls/certs/web.crt

# Re-request with correct CA
sudo ipa-getcert request \
  -c IPA \  # Specify correct CA
  -f /etc/pki/tls/certs/web.crt \
  -k /etc/pki/tls/private/web.key \
  -K HTTP/$(hostname -f)@REALM

30.7 certmonger Database Corruption

Rare but Serious Issue

Symptom: certmonger completely broken, all certs show errors

Diagnosis:

# Check database
ls -l /var/lib/certmonger/

# Check for corruption
sudo journalctl -u certmonger | grep -i corrupt

Solution (Nuclear Option):

# CAUTION: This removes all tracking!

# Step 1: Backup current state
sudo tar czf certmonger-backup-$(date +%Y%m%d).tar.gz \
  /var/lib/certmonger/ \
  /etc/pki/tls/

# Step 2: Document current tracking
sudo getcert list > /tmp/certmonger-list-backup.txt

# Step 3: Stop certmonger
sudo systemctl stop certmonger

# Step 4: Remove database
sudo rm -rf /var/lib/certmonger/cas/*
sudo rm -rf /var/lib/certmonger/requests/*

# Step 5: Start certmonger
sudo systemctl start certmonger

# Step 6: Re-add certificates (from backup documentation)
# Manually re-request each certificate

30.8 Debugging certmonger

Enable Debug Logging

#============================================#
# CERTMONGER DEBUG MODE
#============================================#

# Edit service file
sudo systemctl edit certmonger

# Add:
[Service]
Environment="G_MESSAGES_DEBUG=all"

# Reload and restart
sudo systemctl daemon-reload
sudo systemctl restart certmonger

# Watch detailed logs
sudo journalctl -u certmonger -f

# Disable debug after troubleshooting
sudo systemctl revert certmonger
sudo systemctl restart certmonger

Manual Cert Request Test

#============================================#
# TEST CERTIFICATE REQUEST MANUALLY
#============================================#

# Submit request and watch
sudo ipa-getcert request \
  -f /tmp/test.crt \
  -k /tmp/test.key \
  -K HTTP/$(hostname -f)@REALM \
  -v  # Verbose

# Watch in another terminal
sudo journalctl -u certmonger -f

# If successful, remove test
sudo getcert stop-tracking -f /tmp/test.crt -r
rm -f /tmp/test.{crt,key}

30.9 Common Scenarios

Scenario 1: All Certificates Show CA_UNREACHABLE

Likely Cause: IPA server down or network issue

Quick Fix:

# Check IPA
ipa ping

# If down, fix IPA first
ssh ipa-server "sudo ipactl start"

# If network issue, fix network

# Restart certmonger
sudo systemctl restart certmonger

Scenario 2: One Certificate Stuck

Diagnosis:

# Check specific certificate
sudo getcert list -f /etc/pki/tls/certs/problem.crt

# Try resubmitting
sudo ipa-getcert resubmit -f /etc/pki/tls/certs/problem.crt

# If still stuck, recreate request
sudo getcert stop-tracking -f /etc/pki/tls/certs/problem.crt
sudo ipa-getcert request \
  -f /etc/pki/tls/certs/problem.crt \
  -k /etc/pki/tls/private/problem.key \
  -K HTTP/$(hostname -f)@REALM \
  -D $(hostname -f)

Scenario 3: Certificate Renewed but Service Not Reloaded

Symptom: New cert exists but service still uses old one

Cause: Post-save command failed or not configured

Solution:

# Check post-save command
sudo getcert list -f /etc/pki/tls/certs/web.crt | grep "post-save"

# If missing, add it (re-create tracking entry; do not use getcert rekey)
sudo getcert stop-tracking -f /etc/pki/tls/certs/web.crt
sudo getcert start-tracking \
  -f /etc/pki/tls/certs/web.crt \
  -k /etc/pki/tls/private/web.key \
  -C "systemctl reload httpd"

# Test post-save command works
sudo systemctl reload httpd

# Force renewal to test
sudo ipa-getcert resubmit -f /etc/pki/tls/certs/web.crt

30.10 Key Takeaways

  1. CA_UNREACHABLE is most common issue - Check IPA connectivity
  2. CA_REJECTED means principal problem - Create service principal
  3. MONITORING status means all is well
  4. Post-save commands critical - Test them independently
  5. certmonger logs in journal - Use journalctl -u certmonger
  6. Retry with resubmit - Often fixes transient issues
  7. Check Kerberos tickets - Expired tickets cause issues

Quick Reference Card

┌─────────────────────────────────────────────────────────────┐
│ CERTMONGER TROUBLESHOOTING QUICK REFERENCE                  │
├─────────────────────────────────────────────────────────────┤
│ Status:          getcert list                               │
│ Verbose:         getcert list -v                            │
│ Specific:        getcert list -f /path/to/cert.crt          │
│ Logs:            journalctl -u certmonger -f                │
│                                                             │
│ Resubmit:        ipa-getcert resubmit -f /path/to/cert.crt  │
│ Stop track:      getcert stop-tracking -f /path/to/cert.crt │
│ Start track:     getcert start-tracking -f cert -k key      │
│                                                             │
│ CA_UNREACHABLE:  Check: ipa ping, klist                     │
│                  Fix: kinit -k host/$(hostname -f)@REALM    │
│                                                             │
│ CA_REJECTED:     Check: ipa service-show SERVICE/host       │
│                  Fix: ipa service-add SERVICE/host          │
│                                                             │
│ Debug:           systemctl edit certmonger                  │
│                  Environment="G_MESSAGES_DEBUG=all"         │
└─────────────────────────────────────────────────────────────┘

✅ MONITORING = All good!
❌ CA_UNREACHABLE = Check IPA connectivity
❌ CA_REJECTED = Check service principal

Chapter Navigation

Chapter 31: Crypto-Policy Troubleshooting

RHEL 8/9/10 Only: Crypto-policies are powerful but can cause compatibility issues. Learn how to diagnose and fix crypto-policy problems.


31.1 Crypto-Policy Overview

Available: RHEL 8, 9, 10 only (NOT RHEL 7)

Quick Check:

# Check if crypto-policies available
which update-crypto-policies

# If found: RHEL 8/9/10
# If not found: RHEL 7 (no crypto-policies)

# Current policy
update-crypto-policies --show

31.2 Common Crypto-Policy Issues

Issue 1: Application Fails After Policy Change

Symptom: Service worked, then you changed crypto-policy, now it fails

Scenario:

# Before
update-crypto-policies --show
# DEFAULT

# You changed it
sudo update-crypto-policies --set FUTURE
sudo systemctl restart httpd

# Now httpd won't start or clients can't connect

Diagnosis:

#============================================#
# DIAGNOSE POLICY CHANGE IMPACT
#============================================#

# Step 1: Check what changed
cat /etc/crypto-policies/back-ends/opensslcnf.config

# Step 2: Check logs
sudo journalctl -xe -u httpd | grep -i cipher

# Step 3: Test connection
openssl s_client -connect localhost:443

# Step 4: Check if app overriding policy
grep -r "SSLProtocol\|SSLCipherSuite" /etc/httpd/

Solution:

# Solution 1: Revert policy
sudo update-crypto-policies --set DEFAULT
sudo systemctl restart httpd

# Solution 2: Fix application config
# Remove hard-coded cipher specifications
# Let crypto-policy handle it

# Solution 3: Create custom policy module (RHEL 9+)
# See Chapter 23 for details

Issue 2: “no shared cipher”

Symptom: Clients can’t connect after policy change

Full Error:

SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure
no shared cipher

Diagnosis:

#============================================#
# DIAGNOSE CIPHER MISMATCH
#============================================#

# Step 1: Check current policy
update-crypto-policies --show
# FUTURE  ← Very strict!

# Step 2: What ciphers are available?
openssl ciphers -v | head -20

# Step 3: Test client capabilities
openssl s_client -connect server:443 -cipher 'ALL'

# Step 4: Is client too old?
# Old client might only support weak ciphers blocked by FUTURE policy

Solutions:

# Solution 1: Use less strict policy (temporary!)
sudo update-crypto-policies --set DEFAULT
sudo systemctl restart services

# Solution 2: Update client to support modern ciphers

# Solution 3: Create custom policy module
# Allow specific cipher for compatibility

Issue 3: TLS 1.0/1.1 Client Can’t Connect

Symptom: Old clients fail to connect to RHEL 8+ server

Error:

SSL routines:ssl3_read_bytes:tlsv1 alert protocol version
wrong version number

Diagnosis:

# Check policy
update-crypto-policies --show
# DEFAULT  ← Blocks TLS 1.0/1.1

# Test if TLS 1.0 works
openssl s_client -connect server:443 -tls1
# Should fail with DEFAULT policy

# Test if TLS 1.2 works
openssl s_client -connect server:443 -tls1_2
# Should work

Solutions:

# Solution 1: Temporary LEGACY policy (NOT recommended!)
sudo update-crypto-policies --set LEGACY
sudo systemctl restart services
# Now TLS 1.0/1.1 allowed

# Solution 2: Update client to support TLS 1.2+
# This is the PROPER fix

# Solution 3: Per-application override (last resort)
# Apache example:
# SSLProtocol all -SSLv3  # Re-enables TLS 1.0/1.1

Issue 4: Service Overriding Crypto-Policy

Symptom: Policy changes don’t affect service

Diagnosis:

#============================================#
# CHECK FOR POLICY OVERRIDES
#============================================#

# Apache
grep -r "SSLProtocol\|SSLCipherSuite" /etc/httpd/

# NGINX
grep -r "ssl_protocols\|ssl_ciphers" /etc/nginx/

# Postfix
sudo postconf | grep -E "smtp.*_tls_protocols|smtp.*_tls_ciphers"

# If found → Service is overriding policy!

Solution:

# Remove overrides from config files
# Let crypto-policy handle TLS settings

# Apache: Remove or comment out
# #SSLProtocol all -SSLv3
# #SSLCipherSuite ...

# NGINX: Remove
# #ssl_protocols ...
# #ssl_ciphers ...

# Restart service
sudo systemctl restart httpd

31.3 Crypto-Policy Not Applied

Policy Set But Not Taking Effect

Symptoms:

  • Changed policy but services still use old settings
  • Weak ciphers still accepted

Diagnosis:

#============================================#
# VERIFY POLICY IS ACTIVE
#============================================#

# Step 1: Confirm policy set
update-crypto-policies --show

# Step 2: Check when policy was last updated
ls -l /etc/crypto-policies/back-ends/

# Step 3: Check if services restarted
systemctl status httpd nginx postfix | grep "Active:"
# Services MUST be restarted after policy change!

# Step 4: Test actual ciphers in use
openssl s_client -connect localhost:443 | grep "Cipher"

Solution:

# Restart ALL services
sudo systemctl restart httpd nginx postfix slapd

# Or reboot (ensures everything picks up changes)
sudo reboot

# Verify after restart
openssl s_client -connect localhost:443

31.4 FIPS Policy Issues

FIPS Policy Failures

Symptom: Services fail in FIPS mode

Diagnosis:

#============================================#
# DIAGNOSE FIPS ISSUES
#============================================#

# Step 1: Verify FIPS mode enabled
fips-mode-setup --check

# Step 2: Check crypto-policy
update-crypto-policies --show
# Should show: FIPS

# Step 3: Check for non-FIPS algorithms
# Common culprits: MD5, SHA-1, weak ciphers

# Step 4: Test with FIPS provider
openssl list -providers | grep fips

Common FIPS Issues:

# Issue: Application uses MD5 (not FIPS-approved)
# Error: "digital envelope routines:EVP_DigestInit_ex:disabled for fips"

# Solution: Update application to use SHA-256

# Issue: Certificate has SHA-1 signature
# Error: "ca md too weak"

# Solution: Reissue certificate with SHA-256 or better

31.5 Policy Compatibility Testing

Before Changing Policy

#!/bin/bash
# test-crypto-policy-change.sh
# Test crypto-policy change before production

NEW_POLICY=$1  # DEFAULT, LEGACY, FUTURE, or FIPS

if [ -z "$NEW_POLICY" ]; then
  echo "Usage: $0 <policy>"
  exit 1
fi

echo "=== Testing Crypto-Policy Change to $NEW_POLICY ==="

# Save current policy
CURRENT=$(update-crypto-policies --show)
echo "Current policy: $CURRENT"

# Change policy
echo "Changing to $NEW_POLICY..."
sudo update-crypto-policies --set "$NEW_POLICY"

# Restart services
echo "Restarting services..."
sudo systemctl restart httpd nginx postfix 2>/dev/null

# Wait for services to start
sleep 3

# Test each service
echo ""
echo "Testing services:"

# Apache
if systemctl is-active --quiet httpd; then
  curl -ks https://localhost/ >/dev/null && \
    echo "✅ Apache: OK" || echo "❌ Apache: FAILED"
else
  echo "❌ Apache: Not running"
fi

# NGINX
if systemctl is-active --quiet nginx; then
  curl -ks https://localhost:8443/ >/dev/null && \
    echo "✅ NGINX: OK" || echo "❌ NGINX: FAILED"
else
  echo "⚠️ NGINX: Not installed"
fi

# Postfix
if systemctl is-active --quiet postfix; then
  timeout 3 openssl s_client -starttls smtp -connect localhost:25 </dev/null &>/dev/null && \
    echo "✅ Postfix: OK" || echo "❌ Postfix: FAILED"
else
  echo "⚠️ Postfix: Not installed"
fi

# Ask to keep or revert
echo ""
read -p "Keep $NEW_POLICY policy? (y/n): " KEEP

if [ "$KEEP" != "y" ]; then
  echo "Reverting to $CURRENT..."
  sudo update-crypto-policies --set "$CURRENT"
  sudo systemctl restart httpd nginx postfix 2>/dev/null
  echo "✅ Reverted"
else
  echo "✅ Keeping $NEW_POLICY policy"
fi

31.6 Troubleshooting Workflow

Systematic Approach

Crypto-Policy Issue?
    │
    ├─ Step 1: Identify current policy
    │   └─ update-crypto-policies --show
    │
    ├─ Step 2: Check if policy changed recently
    │   └─ Check /var/log/messages for "crypto-policies"
    │
    ├─ Step 3: Test with different policy
    │   └─ sudo update-crypto-policies --set LEGACY
    │   └─ If works → policy was too strict
    │
    ├─ Step 4: Identify incompatibility
    │   └─ openssl s_client -cipher 'ALL' -tls1
    │   └─ Find what client/server needs
    │
    ├─ Step 5: Choose fix
    │   ├─ A) Update client (best)
    │   ├─ B) Create custom module (good)
    │   ├─ C) Use less strict policy (acceptable)
    │   └─ D) Per-app override (last resort)
    │
    └─ Step 6: Test and document
        └─ Verify fix works
        └─ Document why change needed

31.7 Debugging Crypto-Policy Application

Verify Policy Is Applied

#============================================#
# VERIFY CRYPTO-POLICY APPLICATION
#============================================#

# Step 1: Check policy
update-crypto-policies --show

# Step 2: Check back-end files were updated
ls -l /etc/crypto-policies/back-ends/
# Files should be recently modified

# Step 3: View OpenSSL configuration
cat /etc/crypto-policies/back-ends/opensslcnf.config

# Step 4: Test actual cipher availability
openssl ciphers -v | grep -E "TLS|SSL"

# Step 5: Test connection
openssl s_client -connect localhost:443
# Look for: Protocol version, Cipher

# Step 6: Check if service restarted since policy change
systemctl status httpd | grep "Active:"
# Should show recent activation time

31.8 Common Scenarios

Scenario 1: Legacy Application After RHEL 8 Upgrade

Problem: App worked on RHEL 7, fails on RHEL 8

Root Cause: RHEL 7 had no crypto-policies, RHEL 8 DEFAULT blocks TLS 1.0/1.1

Solution:

# Quick fix (temporary!):
sudo update-crypto-policies --set LEGACY

# Proper fix:
# Update application to support TLS 1.2+

# Document exception
echo "Application X requires LEGACY policy due to TLS 1.0 requirement" > \
  /etc/crypto-policies/POLICY-EXCEPTION.txt

Scenario 2: Can’t Connect to Windows Server 2008

Problem: RHEL 9 can’t connect to old Windows server

Cause: Windows Server 2008 only supports TLS 1.0

Solutions:

# Option 1: Upgrade Windows (best)

# Option 2: LEGACY policy (temporary)
sudo update-crypto-policies --set LEGACY

# Option 3: Custom policy module for this specific case
# See Chapter 23

31.9 Key Takeaways

  1. Crypto-policies are RHEL 8+ only (not RHEL 7)
  2. Services MUST restart after policy change
  3. Policy changes are system-wide - Affect everything
  4. DEFAULT is recommended for most environments
  5. LEGACY should be temporary only
  6. Test before deploying new policies
  7. Update clients rather than weaken policy

Quick Reference Card

┌───────────────────────────────────────────────────────────────┐
│ CRYPTO-POLICY TROUBLESHOOTING                                 │
├───────────────────────────────────────────────────────────────┤
│ Check:         update-crypto-policies --show                  │
│ Set:           sudo update-crypto-policies --set <POLICY>     │
│ Revert:        sudo update-crypto-policies --set DEFAULT      │
│                                                               │
│ Back-ends:     /etc/crypto-policies/back-ends/                │
│ OpenSSL:       cat .../back-ends/opensslcnf.config            │
│                                                               │
│ Test:          openssl ciphers -v                             │
│                openssl s_client -connect :443                 │
│                                                               │
│ After change:  sudo systemctl restart <all-services>          │
│                OR: sudo reboot                                │
│                                                               │
│ Debug:         grep -r "SSLProtocol\|ssl_protocols" /etc/     │
│                (look for overrides)                           │
└───────────────────────────────────────────────────────────────┘

⚠️ RHEL 7 doesn't have crypto-policies
✅ Always restart services after policy change
✅ DEFAULT works for 95% of cases

Chapter Navigation

Chapter 32: SOS Report Analysis

Support Essential: SOS reports are RHEL’s system diagnostic tool. Learn how to extract certificate information from SOS reports for troubleshooting.


32.1 What is an SOS Report?

sosreport is Red Hat’s diagnostic data collection tool.

Contains:

  • ✅ System configuration files
  • ✅ Log files
  • ✅ Command outputs
  • ✅ Package lists
  • ✅ Certificate information
  • ✅ Security settings
  • ❌ Private keys (excluded for security!)

Use Cases:

  • Opening Red Hat support cases
  • Post-incident analysis
  • Pre-migration audits
  • Security compliance checks

32.2 Generating an SOS Report

Basic SOS Report Generation

#============================================#
# GENERATE SOS REPORT
#============================================#

# Install sos (usually pre-installed)
sudo dnf install sos -y

# Generate report
sudo sos report

# Interactive prompts:
# - Case ID (optional)
# - Description
# - Confirm

# Output:
# /var/tmp/sosreport-hostname-YYYYMMDDHHMMSS.tar.xz

# Extract
tar xf /var/tmp/sosreport-*.tar.xz
cd sosreport-*/

Certificate-Focused SOS Report

#============================================#
# SOS REPORT WITH CERTIFICATE FOCUS
#============================================#

# Generate with specific plugins
sudo sos report \
  --batch \
  --enable-plugins crypto,openssl,certmonger,freeipa \
  --case-id "CASE12345"

# Or specify what to include
sudo sos report \
  --batch \
  -o crypto \
  -o openssl \
  -o certmonger \
  -o pki

32.3 Finding Certificate Information in SOS

Key Locations in SOS Report

#============================================#
# CERTIFICATE-RELATED FILES IN SOS REPORT
#============================================#

# After extracting sosreport-*.tar.xz:
cd sosreport-*/

# Certificate files (public only, no private keys!)
ls -la etc/pki/tls/certs/
ls -la etc/pki/ca-trust/source/anchors/

# certmonger tracking
cat sos_commands/certmonger/getcert_list

# OpenSSL version
cat sos_commands/crypto/openssl_version

# Crypto-policy (RHEL 8+)
cat sos_commands/crypto/update-crypto-policies_--show

# Trust store
ls -la etc/pki/ca-trust/extracted/

# Service configurations
cat etc/httpd/conf.d/ssl.conf
cat etc/nginx/nginx.conf
cat etc/postfix/main.cf | grep tls

# Certificate expiration check
cat sos_commands/crypto/openssl_x509_-in_*

# System info
cat etc/redhat-release
cat sos_commands/kernel/uname_-a

32.4 Analyzing Certificate Issues from SOS

Certificate Expiration Analysis

#============================================#
# CHECK CERTIFICATE EXPIRATION IN SOS
#============================================#

# Navigate to SOS report directory
cd sosreport-hostname-*/

# Find all certificate inspection outputs
find sos_commands/crypto/ -name "*x509*" -type f

# Check each certificate
for cert_output in sos_commands/crypto/openssl_x509_*.txt; do
  echo "=== $cert_output ==="
  grep -E "(Subject:|Not After)" "$cert_output"
  echo ""
done

# Or extract expiration dates
grep -r "Not After" sos_commands/crypto/ | sort

certmonger Status Analysis

#============================================#
# ANALYZE CERTMONGER FROM SOS
#============================================#

# certmonger list output
cat sos_commands/certmonger/getcert_list

# Look for:
# - status: CA_UNREACHABLE  ← Problem!
# - status: CA_REJECTED     ← Problem!
# - expires: <date>         ← Check if soon

# Count certificates by status
grep "status:" sos_commands/certmonger/getcert_list | sort | uniq -c

# Find problematic certificates
grep -B10 "CA_UNREACHABLE\|CA_REJECTED" sos_commands/certmonger/getcert_list

Crypto-Policy Analysis (RHEL 8+)

#============================================#
# CHECK CRYPTO-POLICY IN SOS
#============================================#

# Current policy
cat sos_commands/crypto/update-crypto-policies_--show

# Check for overrides
grep -r "SSLProtocol\|SSLCipherSuite" etc/httpd/
grep -r "ssl_protocols\|ssl_ciphers" etc/nginx/
grep -r "tls_protocols" etc/postfix/main.cf

# If overrides found: Document that service opts out of crypto-policy

32.5 Common SOS Report Findings

Finding 1: Expired Certificates

In SOS Report:

# Check certificate expirations
grep "Not After" sos_commands/crypto/* | \
  while read line; do
    # Parse and check if expired
    echo "$line"
  done

Red Flags:

  • Certificates expired before SOS report generation
  • Certificates expiring within 30 days
  • Multiple expired certificates

Finding 2: certmonger Issues

In SOS Report:

# Check certmonger status
cat sos_commands/certmonger/getcert_list | grep -A15 "Request ID"

# Common issues:
# - Multiple CA_UNREACHABLE (IPA connectivity issue)
# - CA_REJECTED (permissions/principal issue)
# - Old expiration dates with no renewal (certmonger not working)

Finding 3: Missing Intermediate Certificates

In SOS Report:

# Check certificate chain
# If service config points to cert without intermediate:
grep "SSLCertificateFile" etc/httpd/conf.d/ssl.conf
# /etc/pki/tls/certs/server.crt  ← Check if this includes chain

# Check actual certificate
openssl x509 -in etc/pki/tls/certs/server.crt -noout -text
# Look for: Issuer (if not well-known, need intermediate)

32.6 SOS Report Checklist for Certificates

Systematic Analysis

## SOS Report Certificate Analysis Checklist

### System Information
- [ ] RHEL version (`cat etc/redhat-release`)
- [ ] OpenSSL version (`cat sos_commands/crypto/openssl_version`)
- [ ] Crypto-policy (`cat sos_commands/crypto/update-crypto-policies*`)
- [ ] FIPS mode (`grep FIPS sos_commands/crypto/*`)

### Certificate Files
- [ ] List certificates (`ls etc/pki/tls/certs/`)
- [ ] Check permissions (`ls -la etc/pki/tls/private/`)
- [ ] Verify ownership
- [ ] Check SELinux contexts (`ls -Z etc/pki/tls/`)

### Certificate Validity
- [ ] Check expirations (`grep "Not After" sos_commands/crypto/*`)
- [ ] Identify expired certificates
- [ ] Identify certificates expiring soon (< 30 days)
- [ ] Check signature algorithms (SHA-1 = problem on RHEL 9+)

### certmonger Status (if used)
- [ ] certmonger running? (`cat sos_commands/systemd/systemctl_list-units`)
- [ ] Tracked certificates (`cat sos_commands/certmonger/getcert_list`)
- [ ] Any CA_UNREACHABLE or CA_REJECTED?
- [ ] Renewal schedule appropriate?

### Service Configurations
- [ ] Apache SSL config (`cat etc/httpd/conf.d/ssl.conf`)
- [ ] NGINX SSL config (`cat etc/nginx/nginx.conf`)
- [ ] Postfix TLS config (`grep tls etc/postfix/main.cf`)
- [ ] OpenLDAP TLS config
- [ ] Certificate paths correct?

### Trust Store
- [ ] Custom CAs (`ls etc/pki/ca-trust/source/anchors/`)
- [ ] Trust bundle updated
- [ ] Blacklisted certs (RHEL 8+)

### Logs
- [ ] Recent certificate errors (`grep -i cert var/log/messages`)
- [ ] SSL/TLS errors in service logs
- [ ] SELinux denials (`grep AVC var/log/audit/audit.log | grep cert`)

### Recommendations
- [ ] List certificate issues found
- [ ] Prioritize by severity
- [ ] Suggest remediation steps

32.7 Automated SOS Analysis Script

Certificate Issue Finder

#!/bin/bash
# analyze-sos-certificates.sh
# Automated certificate issue detection in SOS reports

SOS_DIR=$1

if [ -z "$SOS_DIR" ] || [ ! -d "$SOS_DIR" ]; then
  echo "Usage: $0 /path/to/sosreport-directory"
  exit 1
fi

cd "$SOS_DIR"

echo "=== SOS Report Certificate Analysis ==="
echo "Report: $(basename $SOS_DIR)"
echo ""

# System info
echo "System Information:"
echo "  RHEL Version: $(cat etc/redhat-release 2>/dev/null)"
echo "  OpenSSL: $(cat sos_commands/crypto/openssl_version 2>/dev/null | head -2)"
if [ -f sos_commands/crypto/update-crypto-policies_--show ]; then
  echo "  Crypto-Policy: $(cat sos_commands/crypto/update-crypto-policies_--show)"
fi
echo ""

# Certificate expiration
echo "Certificate Expiration:"
if [ -d sos_commands/crypto ]; then
  grep -h "Not After" sos_commands/crypto/openssl_x509_* 2>/dev/null | \
    while read line; do
      echo "  $line"
    done
else
  echo "  No certificate data found"
fi
echo ""

# certmonger status
echo "certmonger Status:"
if [ -f sos_commands/certmonger/getcert_list ]; then
  STATUS_COUNT=$(grep "status:" sos_commands/certmonger/getcert_list | sort | uniq -c)
  echo "$STATUS_COUNT"

  # Highlight problems
  if grep -q "CA_UNREACHABLE\|CA_REJECTED" sos_commands/certmonger/getcert_list; then
    echo "  ⚠️ Issues found:"
    grep -B5 "CA_UNREACHABLE\|CA_REJECTED" sos_commands/certmonger/getcert_list | \
      grep -E "(Request ID|status:)" | head -20
  fi
else
  echo "  certmonger not installed or no data"
fi
echo ""

# Check for common issues
echo "Potential Issues:"
ISSUES=0

# Expired certs (basic check)
if grep -q "Not After.*202[0-3]" sos_commands/crypto/* 2>/dev/null; then
  echo "  ⚠️ Potentially expired certificates found"
  ((ISSUES++))
fi

# certmonger problems
if grep -q "CA_UNREACHABLE" sos_commands/certmonger/getcert_list 2>/dev/null; then
  echo "  ⚠️ certmonger CA_UNREACHABLE status found"
  ((ISSUES++))
fi

# SELinux denials
if grep -q "avc.*denied.*cert" var/log/audit/audit.log 2>/dev/null; then
  echo "  ⚠️ SELinux denials related to certificates"
  ((ISSUES++))
fi

if [ $ISSUES -eq 0 ]; then
  echo "  ✅ No obvious issues detected"
fi

echo ""
echo "=== Analysis Complete ==="

32.8 Key Files to Check in SOS

Critical Certificate Files

sosreport-hostname-YYYYMMDDHHMMSS/
├── etc/
│   ├── pki/
│   │   ├── tls/certs/                     ← Certificates (public)
│   │   ├── ca-trust/                      ← Trust store
│   │   └── nssdb/                         ← NSS databases
│   ├── httpd/conf.d/ssl.conf              ← Apache config
│   ├── nginx/nginx.conf                   ← NGINX config
│   └── postfix/main.cf                    ← Postfix config
│
├── sos_commands/
│   ├── crypto/
│   │   ├── openssl_version                ← OpenSSL version
│   │   ├── openssl_x509_*                 ← Certificate inspections
│   │   └── update-crypto-policies_--show  ← Policy
│   │
│   ├── certmonger/
│   │   └── getcert_list                   ← certmonger status
│   │
│   ├── systemd/
│   │   └── systemctl_list-units           ← Service status
│   │
│   └── networking/
│       └── ss_-tulpn                      ← Listening ports
│
└── var/log/
    ├── messages                           ← System log
    ├── httpd/ssl_error_log                ← Apache SSL errors
    └── audit/audit.log                    ← SELinux denials

32.9 Common SOS Report Scenarios

Scenario 1: Website Down - Certificate Issue?

Analysis Steps:

# 1. Check if httpd was running
grep "httpd.service" sos_commands/systemd/systemctl_list-units
# active (running) ← Service was up

# 2. Check SSL error log
tail var/log/httpd/ssl_error_log
# Look for certificate-related errors

# 3. Check certificate expiration
cat sos_commands/crypto/openssl_x509_*server.crt* | grep "Not After"

# 4. Check Apache config
cat etc/httpd/conf.d/ssl.conf | grep -E "SSLCertificate"

# 5. Check if files existed
ls -l etc/pki/tls/certs/ | grep server

Scenario 2: certmonger Renewal Failures

Analysis Steps:

# 1. Check certmonger status
cat sos_commands/certmonger/getcert_list

# 2. Look for CA_UNREACHABLE
grep "CA_UNREACHABLE" sos_commands/certmonger/getcert_list

# 3. Check IPA connectivity (if using FreeIPA)
grep "ipa" var/log/messages | tail -50

# 4. Check Kerberos tickets
cat sos_commands/kerberos/klist* 2>/dev/null

# 5. Identify when renewal should have happened
# Look for expiration dates, calculate 2/3 of lifetime

32.10 Key Takeaways

  1. SOS reports are invaluable for remote troubleshooting
  2. No private keys included (security!)
  3. Certificate information IS included (public certs, config, logs)
  4. certmonger status preserved in getcert_list output
  5. Crypto-policy recorded (RHEL 8+)
  6. Use for post-incident analysis and audits
  7. Automate analysis with scripts

Quick Reference Card

┌──────────────────────────────────────────────────────────────┐
│ SOS REPORT CERTIFICATE ANALYSIS                              │
├──────────────────────────────────────────────────────────────┤
│ Generate:   sudo sos report                                  │
│ Extract:    tar xf sosreport-*.tar.xz                        │
│                                                              │
│ Key Files:  etc/pki/tls/certs/                               │
│             etc/httpd/conf.d/ssl.conf                        │
│             sos_commands/certmonger/getcert_list             │
│             sos_commands/crypto/openssl_version              │
│             sos_commands/crypto/update-crypto-policies*      │
│             var/log/httpd/ssl_error_log                      │
│                                                              │
│ Common checks:                                               │
│   - Certificate expiration dates                             │
│   - certmonger status                                        │
│   - Service configurations                                   │
│   - crypto-policy setting                                    │
│   - SELinux denials                                          │
└──────────────────────────────────────────────────────────────┘

⚠️ Private keys NOT included (security)
✅ Perfect for remote troubleshooting

Chapter Navigation

Chapter 33: Emergency Procedures

Production Down: When certificates break and services are offline, you need quick, reliable procedures. This chapter is your emergency playbook.


33.1 Emergency Response Philosophy

When production is down:

  • Speed matters - Every minute counts
  • 🎯 Fix first, investigate later - Get services running
  • 📝 Document everything - For post-mortem
  • 🔄 Temporary is OK - Proper fix comes after recovery

This chapter provides:

  • Quick diagnostic procedures
  • Emergency workarounds
  • Temporary certificates
  • Roll back procedures
  • Communication templates

33.2 Quick Diagnostic (First 60 Seconds)

Triage Questions

#============================================#
# EMERGENCY TRIAGE - 60 SECONDS
#============================================#

# Q1: What's broken?
systemctl status httpd nginx postfix

# Q2: When did it break?
journalctl -xe --since "10 minutes ago" | grep -i cert

# Q3: Certificate expired?
openssl x509 -in /etc/pki/tls/certs/server.crt -noout -dates

# Q4: Recent changes?
rpm -qa --last | head -20        # Recent package updates
ausearch -m SYSCALL --start recent | grep cert  # Recent cert file access

# Q5: Disk full?
df -h /etc/pki

# Q6: SELinux blocking?
ausearch -m avc -ts recent | grep cert

Decision Tree (First Response)

Certificate Issue Detected
    │
    ├─ Service won't start?
    │   ├─ File not found → Quick Fix #1: Restore from backup
    │   ├─ Permission denied → Quick Fix #2: Fix permissions
    │   └─ Invalid cert → Quick Fix #3: Use temporary cert
    │
    ├─ Certificate expired?
    │   └─ Quick Fix #4: Generate temp self-signed OR restore backup
    │
    ├─ Chain validation failure?
    │   └─ Quick Fix #5: Add missing CA OR use LEGACY policy
    │
    └─ Unknown/Complex?
        └─ Escalate + Apply Quick Fix #6: Rollback to last known good

33.3 Quick Fix #1: Restore from Backup

Scenario: Certificate/key file missing or corrupted

Time: 2-5 minutes

#!/bin/bash
# emergency-restore-cert.sh

SERVICE=$1  # apache, nginx, postfix, etc.
BACKUP_DIR="/var/backups/certificates"

echo "=== EMERGENCY: Restoring $SERVICE Certificate ==="

# Stop service
systemctl stop $SERVICE

# Find most recent backup
LATEST=$(ls -dt $BACKUP_DIR/*/ | head -2)
echo "Using backup from: $LATEST"

# Restore certificate
if [ -f "$LATEST/${SERVICE}.crt" ]; then
  cp "$LATEST/${SERVICE}.crt" /etc/pki/tls/certs/
  chmod 644 /etc/pki/tls/certs/${SERVICE}.crt
  echo "✅ Certificate restored"
else
  echo "❌ No backup found for $SERVICE"
  exit 1
fi

# Restore key
if [ -f "$LATEST/${SERVICE}.key" ]; then
  cp "$LATEST/${SERVICE}.key" /etc/pki/tls/private/
  chmod 600 /etc/pki/tls/private/${SERVICE}.key
  echo "✅ Private key restored"
fi

# Start service
systemctl start $SERVICE

# Test
sleep 2
systemctl status $SERVICE

if systemctl is-active --quiet $SERVICE; then
  echo "✅ SUCCESS: $SERVICE is running"
  exit 0
else
  echo "❌ FAILED: $SERVICE did not start"
  journalctl -xe -u $SERVICE | tail -20
  exit 1
fi

33.4 Quick Fix #2: Fix Permissions Emergency

Scenario: Service fails with “permission denied” on certificate files

Time: 30 seconds

#!/bin/bash
# emergency-fix-permissions.sh

echo "=== EMERGENCY: Fixing Certificate Permissions ==="

# Fix certificate directory
chmod 755 /etc/pki/tls/certs/
chmod 644 /etc/pki/tls/certs/*.crt 2>/dev/null

# Fix private key directory
chmod 711 /etc/pki/tls/private/
chmod 600 /etc/pki/tls/private/*.key 2>/dev/null

# Fix ownership (adjust for your service)
chown root:root /etc/pki/tls/certs/*.crt 2>/dev/null
chown root:root /etc/pki/tls/private/*.key 2>/dev/null

# Fix SELinux contexts
restorecon -Rv /etc/pki/tls/

echo "✅ Permissions fixed"

# Show results
echo ""
echo "Certificate permissions:"
ls -lZ /etc/pki/tls/certs/*.crt 2>/dev/null | head -5

echo ""
echo "Key permissions:"
ls -lZ /etc/pki/tls/private/*.key 2>/dev/null | head -5

33.5 Quick Fix #3: Generate Temporary Self-Signed Certificate

Scenario: Certificate expired or invalid, need immediate fix

Time: 1-2 minutes

⚠️ WARNING: Self-signed certs cause browser warnings! Only for emergency internal use!

#!/bin/bash
# emergency-self-signed-cert.sh

HOSTNAME=${1:-$(hostname -f)}
DAYS=${2:-30}
CERT_PATH="/etc/pki/tls/certs/${HOSTNAME}-temp.crt"
KEY_PATH="/etc/pki/tls/private/${HOSTNAME}-temp.key"

echo "=== EMERGENCY: Generating Temporary Self-Signed Certificate ==="
echo "Hostname: $HOSTNAME"
echo "Valid for: $DAYS days"

# Generate self-signed certificate
openssl req -x509 -nodes -days $DAYS \
  -newkey rsa:2048 \
  -keyout "$KEY_PATH" \
  -out "$CERT_PATH" \
  -subj "/C=US/ST=Emergency/L=Emergency/O=Emergency/CN=$HOSTNAME" \
  -addext "subjectAltName=DNS:$HOSTNAME,DNS:$(hostname -s)"

if [ $? -eq 0 ]; then
  # Set permissions
  chmod 600 "$KEY_PATH"
  chmod 644 "$CERT_PATH"

  echo "✅ Temporary certificate generated"
  echo "   Certificate: $CERT_PATH"
  echo "   Key: $KEY_PATH"
  echo ""
  echo "⚠️ CRITICAL: This is a TEMPORARY fix!"
  echo "   - Request proper certificate immediately"
  echo "   - Document this emergency action"
  echo "   - Plan proper replacement within $DAYS days"
  echo ""
  echo "To use with Apache:"
  echo "  SSLCertificateFile $CERT_PATH"
  echo "  SSLCertificateKeyFile $KEY_PATH"

  # Show certificate
  openssl x509 -in "$CERT_PATH" -noout -text | grep -E "(Subject:|Not After)"
else
  echo "❌ FAILED to generate certificate"
  exit 1
fi

33.6 Quick Fix #4: Emergency Certificate Renewal

Scenario: Certificate expired, need proper renewal ASAP

Time: 5-15 minutes (depends on CA)

#!/bin/bash
# emergency-renew-cert.sh

CERT_PATH=$1
KEY_PATH=$2
HOSTNAME=$3

echo "=== EMERGENCY: Renewing Expired Certificate ==="

# Generate new CSR
CSR_PATH="/tmp/emergency-$(date +%s).csr"

openssl req -new -key "$KEY_PATH" -out "$CSR_PATH" \
  -subj "/CN=$HOSTNAME" \
  -addext "subjectAltName=DNS:$HOSTNAME"

if [ $? -eq 0 ]; then
  echo "✅ CSR generated: $CSR_PATH"
  echo ""
  echo "NEXT STEPS:"
  echo "1. Submit CSR to CA immediately:"
  echo "   cat $CSR_PATH"
  echo ""
  echo "2. While waiting for CA:"
  echo "   - Use temporary self-signed cert (see Quick Fix #3)"
  echo "   - Or restore from backup (see Quick Fix #1)"
  echo ""
  echo "3. Once CA returns certificate:"
  echo "   cp new-cert.crt $CERT_PATH"
  echo "   systemctl reload <service>"

  # If using FreeIPA
  if command -v ipa-getcert &>/dev/null; then
    echo ""
    echo "4. If using FreeIPA, try automatic renewal:"
    echo "   sudo ipa-getcert resubmit -f $CERT_PATH"
  fi
else
  echo "❌ FAILED to generate CSR"
  exit 1
fi

33.7 Quick Fix #5: Trust Chain Emergency

Scenario: “Unable to get local issuer certificate” error

Time: 1-2 minutes

#!/bin/bash
# emergency-fix-trust.sh

CA_CERT=$1  # Path to CA certificate

if [ -z "$CA_CERT" ] || [ ! -f "$CA_CERT" ]; then
  echo "❌ Usage: $0 /path/to/ca-cert.crt"
  exit 1
fi

echo "=== EMERGENCY: Adding CA to Trust Store ==="

# Copy CA to trust anchors
cp "$CA_CERT" /etc/pki/ca-trust/source/anchors/

# Update trust store
update-ca-trust extract

echo "✅ CA added to system trust store"

# Verify
if trust list | grep -q "$(basename "$CA_CERT" .crt)"; then
  echo "✅ VERIFIED: CA is now trusted"
else
  echo "⚠️ Warning: Could not verify CA was added"
fi

# Test certificate validation
echo ""
echo "Test your certificate now:"
echo "  openssl verify /path/to/your/cert.crt"

Alternative: Temporary LEGACY Policy (RHEL 8+)

# If trust issue is due to weak algorithms
# TEMPORARY - revert after proper fix!

echo "=== EMERGENCY: Setting LEGACY Crypto Policy ==="
update-crypto-policies --show  # Save current
sudo update-crypto-policies --set LEGACY
systemctl restart <service>

echo "⚠️ CRITICAL: This is temporary!"
echo "Proper fix required within 24 hours"

33.8 Quick Fix #6: Rollback to Last Known Good

Scenario: Recent change broke everything, need to revert

Time: 2-5 minutes

#!/bin/bash
# emergency-rollback.sh

echo "=== EMERGENCY: Rolling Back to Last Known Good Configuration ==="

# Stop service
systemctl stop httpd

# Backup current (broken) state
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
mkdir -p /var/backups/emergency/$TIMESTAMP
cp -a /etc/pki/tls/certs/*.crt /var/backups/emergency/$TIMESTAMP/ 2>/dev/null
cp -a /etc/pki/tls/private/*.key /var/backups/emergency/$TIMESTAMP/ 2>/dev/null
cp -a /etc/httpd/conf.d/ssl.conf /var/backups/emergency/$TIMESTAMP/ 2>/dev/null

# Restore from last backup
LAST_GOOD="/var/backups/certificates/last-known-good"
if [ -d "$LAST_GOOD" ]; then
  cp -a "$LAST_GOOD"/*.crt /etc/pki/tls/certs/
  cp -a "$LAST_GOOD"/*.key /etc/pki/tls/private/
  cp -a "$LAST_GOOD"/ssl.conf /etc/httpd/conf.d/ 2>/dev/null

  # Fix permissions
  chmod 644 /etc/pki/tls/certs/*.crt
  chmod 600 /etc/pki/tls/private/*.key

  echo "✅ Rolled back to last known good"
else
  echo "❌ No last-known-good backup found!"
  echo "Looking for any recent backup..."
  ls -ldt /var/backups/certificates/*/ | head -5
  exit 1
fi

# Start service
systemctl start httpd

# Verify
sleep 2
if systemctl is-active --quiet httpd; then
  echo "✅ SUCCESS: Service restored"
else
  echo "❌ Service still not starting"
  journalctl -xe -u httpd | tail -20
  exit 1
fi

33.9 Service-Specific Emergency Procedures

Apache (httpd) Emergency Recovery

#============================================#
# APACHE EMERGENCY RECOVERY
#============================================#

# 1. Stop Apache
systemctl stop httpd

# 2. Check configuration syntax
apachectl configtest
# If fails, fix or restore ssl.conf from backup

# 3. Check certificate files exist
ls -l /etc/pki/tls/certs/server.crt
ls -l /etc/pki/tls/private/server.key

# 4. Emergency: Disable SSL temporarily
mv /etc/httpd/conf.d/ssl.conf /etc/httpd/conf.d/ssl.conf.disabled
systemctl start httpd
# Service now runs on HTTP only (port 80)

# 5. Fix certificates, then re-enable SSL
mv /etc/httpd/conf.d/ssl.conf.disabled /etc/httpd/conf.d/ssl.conf
systemctl reload httpd

NGINX Emergency Recovery

#============================================#
# NGINX EMERGENCY RECOVERY
#============================================#

# 1. Stop NGINX
systemctl stop nginx

# 2. Test configuration
nginx -t
# If fails, check which line/file has issue

# 3. Emergency: Comment out SSL config
sed -i 's/^\(\s*ssl_certificate\)/# \1/' /etc/nginx/nginx.conf
sed -i 's/^\(\s*listen.*443\)/# \1/' /etc/nginx/nginx.conf
sed -i 's/^\(\s*listen.*ssl\)/# \1/' /etc/nginx/nginx.conf

# 4. Start on HTTP only
systemctl start nginx

# 5. Fix certificates, restore SSL config
# Uncomment lines or restore from backup
systemctl reload nginx

certmonger Emergency

#============================================#
# CERTMONGER EMERGENCY RECOVERY
#============================================#

# 1. Check certmonger status
systemctl status certmonger
getcert list

# 2. If cert shows CA_UNREACHABLE
# Check IPA connectivity
ipa ping

# 3. Emergency: Stop tracking, manual renewal
REQUEST_ID=$(getcert list | grep "Request ID" | head -1 | awk -F"'" '{print $2}')
getcert stop-tracking -i $REQUEST_ID

# 4. Manual renewal with IPA
ipa-getcert request -f /etc/pki/tls/certs/server.crt \
  -k /etc/pki/tls/private/server.key \
  -D $(hostname -f) \
  -K host/$(hostname -f)@REALM

# 5. If IPA unavailable, use temporary self-signed
./emergency-self-signed-cert.sh

33.10 Communication Templates

Incident Notification (Internal)

Subject: [URGENT] Certificate Issue - <Service> Down

INCIDENT SUMMARY:
- Service: <Apache/NGINX/etc>
- Impact: <Production/Staging> website down
- Started: <Time>
- Status: Investigating / Applying fix / Resolved

ROOT CAUSE:
- Certificate expired on <Date>
- OR: Certificate file permissions incorrect
- OR: CA trust chain missing

IMMEDIATE ACTION TAKEN:
- Temporary self-signed certificate applied
- Service restored at <Time>

NEXT STEPS:
- Request proper certificate from CA
- Replace temporary cert by <Date/Time>
- Post-mortem scheduled for <Date>

WORKAROUND:
- Users may see security warnings (expected)
- Service is functional despite warnings

Customer Communication (External)

Subject: Service Restoration - Brief Outage

Dear Customers,

We experienced a brief service interruption between <Start Time> and
<End Time> due to a certificate configuration issue. The service has
been fully restored.

You may notice a temporary security warning. This is expected and
safe to proceed. We are working to replace the temporary certificate
with a permanent one within the next few hours.

We apologize for any inconvenience.

Status updates: <URL>
Support: <Email/Phone>

33.11 Post-Emergency Checklist

After emergency recovery:

## Post-Emergency Checklist

### Immediate (Within 1 Hour)
- [ ] Service confirmed running
- [ ] Monitoring restored
- [ ] Stakeholders notified
- [ ] Temporary fix documented

### Short-Term (Within 24 Hours)
- [ ] Proper certificate obtained
- [ ] Temporary cert replaced
- [ ] Configuration validated
- [ ] Backups verified working

### Follow-Up (Within 1 Week)
- [ ] Root cause analysis completed
- [ ] Post-mortem document created
- [ ] Prevention measures identified
- [ ] Monitoring/alerting improved
- [ ] Documentation updated
- [ ] Team debriefed

### Prevention
- [ ] Add monitoring for this scenario
- [ ] Update runbooks
- [ ] Schedule earlier renewals
- [ ] Automate if possible
- [ ] Test recovery procedures

33.12 Emergency Contacts and Resources

Keep This Handy

## Emergency Certificate Response Card

### Quick Commands
openssl x509 -in cert.crt -noout -dates      # Check expiry
systemctl status <service>                    # Service status
journalctl -xe -u <service>                   # Recent logs
getcert list                                  # certmonger status

### Emergency Scripts Location
/usr/local/bin/emergency-*.sh

### Backup Location
/var/backups/certificates/

### Last Known Good
/var/backups/certificates/last-known-good/

### CA Information
CA URL: <URL>
CA Contact: <Email/Phone>
FreeIPA Server: <Hostname>

### Escalation
Team Lead: <Name> <Phone>
Manager: <Name> <Phone>
On-Call: <Pager/Phone>

### Documentation
Runbooks: <Wiki URL>
Previous Incidents: <Ticket System>

33.13 Emergency Scenarios Playbook

Scenario 1: Certificate Expired (Production Down)

Impact: HIGH - Service unavailable Time Pressure: Critical Response:

  1. Assess (30 seconds)

    openssl x509 -in /etc/pki/tls/certs/server.crt -noout -dates
    
  2. Quick Fix (2 minutes)

    ./emergency-self-signed-cert.sh $(hostname -f) 30
    # Update service config to use temp cert
    systemctl restart <service>
    
  3. Communicate (5 minutes)

    • Notify stakeholders
    • Update status page
  4. Proper Fix (15-60 minutes)

    # Request new cert from CA
    # OR use certmonger
    ipa-getcert resubmit -f /etc/pki/tls/certs/server.crt
    
  5. Replace temp cert, verify, document

Scenario 2: Wrong Certificate Deployed

Impact: MEDIUM - Service up but errors Time Pressure: Moderate Response:

  1. Stop bleeding - Rollback

    ./emergency-rollback.sh
    
  2. Verify service restored

  3. Identify correct certificate

  4. Deploy correct cert with validation

  5. Document what went wrong

Scenario 3: CA Server Down (Can’t Renew)

Impact: MEDIUM - Future renewals blocked Time Pressure: Depends on cert expiry Response:

  1. Check cert expiry timeline

    openssl x509 -in cert.crt -noout -checkend $((86400*7))
    
  2. If > 7 days: Wait for CA recovery, monitor

  3. If < 7 days:

    • Generate temporary self-signed
    • Contact CA support
    • Escalate to management
  4. Alternative: Use different CA temporarily

Scenario 4: SELinux Blocking Certificates

Impact: LOW-MEDIUM - Service won’t start Time Pressure: Moderate Response:

  1. Check denials

    ausearch -m avc -ts recent | grep cert
    
  2. Quick fix - Relabel

    restorecon -Rv /etc/pki/tls/
    
  3. If persists - Temporary permissive

    setenforce 0  # TEMPORARY!
    systemctl restart <service>
    
  4. Proper fix - Generate policy

    audit2allow -a -M mycert
    semodule -i mycert.pp
    setenforce 1
    

33.14 Emergency Toolkit

Create Emergency Response Kit

#!/bin/bash
# create-emergency-kit.sh
# Creates a portable emergency response kit

KIT_DIR="/root/cert-emergency-kit"
mkdir -p "$KIT_DIR"

# Copy emergency scripts
cp emergency-*.sh "$KIT_DIR/"

# Create quick reference
cat > "$KIT_DIR/QUICK_REFERENCE.txt" << 'EOF'
=== CERTIFICATE EMERGENCY QUICK REFERENCE ===

1. CHECK STATUS
   systemctl status <service>
   openssl x509 -in cert.crt -noout -dates

2. EXPIRED CERT
   ./emergency-self-signed-cert.sh $(hostname -f)

3. MISSING FILES
   ./emergency-restore-cert.sh <service>

4. PERMISSIONS
   ./emergency-fix-permissions.sh

5. ROLLBACK
   ./emergency-rollback.sh

6. LOGS
   journalctl -xe -u <service>
   tail -f /var/log/httpd/ssl_error_log

===========================
Last Updated: $(date)
EOF

# Set permissions
chmod 700 "$KIT_DIR"
chmod 755 "$KIT_DIR"/*.sh

echo "✅ Emergency kit created: $KIT_DIR"
ls -lh "$KIT_DIR"

33.15 Key Takeaways

  1. Speed over perfection in emergencies
  2. Temporary fixes are OK - Fix properly later
  3. Communication is critical - Keep stakeholders informed
  4. Document everything - For post-mortem
  5. Practice emergency procedures - Don’t wait for real incident
  6. Have backups ready - Test them regularly
  7. Know your escalation path - When to call for help
  8. Post-mortem is mandatory - Learn and improve

Quick Reference Card

┌──────────────────────────────────────────────────────────────┐
│ CERTIFICATE EMERGENCY RESPONSE                               │
├──────────────────────────────────────────────────────────────┤
│ EXPIRED CERT:  ./emergency-self-signed-cert.sh $(hostname)   │
│ MISSING FILE:  ./emergency-restore-cert.sh <service>         │
│ PERMISSIONS:   ./emergency-fix-permissions.sh                │
│ TRUST ISSUE:   ./emergency-fix-trust.sh /path/to/ca.crt      │
│ ROLLBACK:      ./emergency-rollback.sh                       │
│                                                              │
│ DISABLE SSL:   mv ssl.conf ssl.conf.disabled                 │
│                systemctl restart <service>                   │
│                                                              │
│ CHECK EXPIRY:  openssl x509 -in cert.crt -noout -dates       │
│ SERVICE LOGS:  journalctl -xe -u <service>                   │
└──────────────────────────────────────────────────────────────┘

⚠️ REMEMBER: Fix first, investigate later!

🧪 Hands-On Lab

Lab 16: Emergency Procedures

Learn rapid certificate recovery techniques for production emergencies

  • 📁 Location: labs/en_US/16-emergency-procedures/
  • ⏱️ Time: 30-40 minutes
  • 🎯 Level: Advanced

Chapter Navigation

Chapter 34: RHEL Migration Planning & Preparation

Plan for Success: RHEL migrations require careful certificate planning. Learn how to audit, prepare, and plan certificate migration to avoid outages.


34.1 Why Certificate Planning Matters

Without Planning:

❌ Upgrade RHEL → Certificates fail validation
❌ Services won't start
❌ Production outage
❌ Rollback required
❌ Failed migration

With Planning:

✅ Pre-audit identifies issues
✅ Certificates prepared in advance
✅ Test migration successful
✅ Production migration smooth
✅ No certificate-related outages

34.2 Pre-Migration Certificate Audit

Complete Certificate Inventory

#!/bin/bash
# pre-migration-cert-audit.sh
# Complete certificate audit before RHEL migration

echo "=== Pre-Migration Certificate Audit ==="
echo "System: $(hostname)"
echo "Current RHEL: $(cat /etc/redhat-release)"
echo "Date: $(date)"
echo ""

# Find all certificates
echo "=== Certificate Inventory ==="
find /etc/pki/tls/certs/ /etc/httpd/ /etc/nginx/ /etc/postfix/ /etc/openldap/ \
  -name "*.crt" -o -name "*.pem" 2>/dev/null | \
  while read cert; do
    if openssl x509 -in "$cert" -noout 2>/dev/null; then
      echo "Certificate: $cert"
      echo "  Subject: $(openssl x509 -in "$cert" -noout -subject)"
      echo "  Issuer: $(openssl x509 -in "$cert" -noout -issuer)"
      echo "  Expires: $(openssl x509 -in "$cert" -noout -enddate | cut -d= -f2)"

      # Check signature algorithm
      SIG_ALG=$(openssl x509 -in "$cert" -noout -text | grep "Signature Algorithm" | head -2)
      echo "  Signature: $SIG_ALG"

      # Check key size
      KEY_SIZE=$(openssl x509 -in "$cert" -noout -text | grep "Public-Key" | grep -oP '\d+')
      echo "  Key Size: $KEY_SIZE bits"

      # Flag issues
      if echo "$SIG_ALG" | grep -qi "sha1"; then
        echo "  ⚠️ WARNING: SHA-1 signature (will fail on RHEL 9+)"
      fi

      if [ "$KEY_SIZE" -lt 2048 ]; then
        echo "  ⚠️ WARNING: Key < 2048 bits (may fail on RHEL 8+)"
      fi

      if ! openssl x509 -in "$cert" -noout -ext subjectAltName 2>/dev/null | grep -q "DNS:"; then
        echo "  ⚠️ WARNING: Certificat missing SAN"
      fi

      # Check expiration
      if ! openssl x509 -in "$cert" -noout -checkend $((86400*90)); then
        echo "  ⚠️ WARNING: Expires within 90 days"
      fi

      echo ""
    fi
  done

# certmonger tracking
echo "=== certmonger Tracked Certificates ==="
if command -v getcert &>/dev/null; then
  sudo getcert list | grep -E "(Request ID|certificate:|status:)"
else
  echo "certmonger not installed"
fi

# Custom CAs
echo ""
echo "=== Custom CAs in Trust Store ==="
ls -la /etc/pki/ca-trust/source/anchors/

# Service configurations
echo ""
echo "=== Service Certificate Configurations ==="
echo "Apache:"
grep -h "SSLCertificate" /etc/httpd/conf.d/*.conf 2>/dev/null | grep -v "^#"

echo ""
echo "NGINX:"
grep -rh "ssl_certificate" /etc/nginx/ 2>/dev/null | grep -v "^#"

echo ""
echo "Postfix:"
sudo postconf | grep -E "smtpd_tls_cert|smtp_tls_cert"

echo ""
echo "=== Audit Complete ==="
echo "Save this output for migration reference!"

34.3 Certificate Issues to Fix Before Migration

Critical Pre-Migration Fixes

Fix 1: SHA-1 Signatures (RHEL 8→9)

# Find SHA-1 signed certificates
for cert in /etc/pki/tls/certs/*.crt; do
  if openssl x509 -in "$cert" -noout -text 2>/dev/null | \
     grep -qi "Signature Algorithm.*sha1"; then
    echo "⚠️ SHA-1: $cert"
  fi
done

# Action: Reissue ALL SHA-1 certificates before migrating to RHEL 9

Fix 2: Small Keys (< 2048 bits)

# Find small keys
for cert in /etc/pki/tls/certs/*.crt; do
  SIZE=$(openssl x509 -in "$cert" -noout -text 2>/dev/null | \
         grep "Public-Key" | grep -oP '\d+')
  if [ "$SIZE" -lt 2048 ] 2>/dev/null; then
    echo "⚠️ Small key ($SIZE): $cert"
  fi
done

# Action: Reissue with 2048+ bit keys

Fix 3: Missing SANs

# Find certificates without SANs
for cert in /etc/pki/tls/certs/*.crt; do
  if ! openssl x509 -in "$cert" -noout -ext subjectAltName 2>/dev/null | grep -q "DNS:"; then
    echo "⚠️ No SANs: $cert"
  fi
done

# Action: Reissue with proper SANs (required for modern browsers)

Fix 4: Expiring Soon

# Find certificates expiring within migration window
for cert in /etc/pki/tls/certs/*.crt; do
  if ! openssl x509 -in "$cert" -noout -checkend $((86400*90)) 2>/dev/null; then
    echo "⚠️ Expiring soon: $cert"
    openssl x509 -in "$cert" -noout -enddate
  fi
done

# Action: Renew before migration to avoid mid-migration expiration

34.4 Backup Strategy

What to Backup

#============================================#
# PRE-MIGRATION CERTIFICATE BACKUP
#============================================#

BACKUP_DIR="/var/backups/pre-migration-$(date +%Y%m%d)"
mkdir -p "$BACKUP_DIR"

# Backup certificates and keys
sudo tar czf "$BACKUP_DIR/certificates.tar.gz" \
  /etc/pki/tls/ \
  /etc/pki/ca-trust/source/anchors/ \
  /etc/pki/nssdb/

# Backup service configurations
sudo tar czf "$BACKUP_DIR/service-configs.tar.gz" \
  /etc/httpd/conf.d/*.conf \
  /etc/nginx/nginx.conf \
  /etc/nginx/conf.d/ \
  /etc/postfix/main.cf \
  /etc/openldap/ \
  /var/lib/pgsql/data/postgresql.conf \
  /var/lib/pgsql/data/pg_hba.conf \
  2>/dev/null

# Backup certmonger database
sudo tar czf "$BACKUP_DIR/certmonger.tar.gz" \
  /var/lib/certmonger/

# Save certmonger list
sudo getcert list > "$BACKUP_DIR/certmonger-list.txt" 2>/dev/null

# Save crypto-policy (RHEL 8+)
update-crypto-policies --show > "$BACKUP_DIR/crypto-policy.txt" 2>/dev/null

# Create inventory CSV
./pre-migration-cert-audit.sh > "$BACKUP_DIR/certificate-inventory.txt"

# Set permissions
sudo chmod 700 "$BACKUP_DIR"

echo "✅ Backup complete: $BACKUP_DIR"
ls -lh "$BACKUP_DIR"

34.5 Testing Plan

Test Environment Setup

## Migration Testing Checklist

### Test Environment
- [ ] Clone production to test VM/container
- [ ] Same RHEL version as production
- [ ] Same certificates (copies, not originals!)
- [ ] Same service configurations
- [ ] Network isolated from production

### Test Migration
- [ ] Run migration on test system
- [ ] Verify all services start
- [ ] Test certificate validation
- [ ] Check crypto-policy (RHEL 7→8/9)
- [ ] Test client connections
- [ ] Verify certmonger tracking (if used)
- [ ] Document any issues

### Issue Resolution
- [ ] Fix issues found in test
- [ ] Update migration plan
- [ ] Re-test
- [ ] Document workarounds

### Production Readiness
- [ ] Test migration successful
- [ ] Issues documented and resolved
- [ ] Rollback plan ready
- [ ] Team trained
- [ ] Maintenance window scheduled

34.6 Migration Timeline

Sample Migration Schedule

Week 1-2: Planning & Audit
├─ Complete certificate inventory
├─ Identify issues (SHA-1, small keys, etc.)
├─ Plan remediation
└─ Set up test environment

Week 3-4: Remediation
├─ Reissue problematic certificates
├─ Update configurations
├─ Test in current environment
└─ Verify automation works

Week 5-6: Testing
├─ Clone production to test
├─ Perform test migration
├─ Validate certificates post-migration
├─ Document issues and fixes
└─ Update migration runbook

Week 7: Pre-Migration Prep
├─ Final certificate audit
├─ Renew expiring certificates
├─ Complete backups
├─ Brief team
└─ Verify rollback plan

Week 8: Migration
├─ Maintenance window
├─ Execute migration
├─ Validate certificates
├─ Monitor for 24-48 hours
└─ Document lessons learned

34.7 Rollback Planning

Certificate Rollback Procedure

#============================================#
# CERTIFICATE ROLLBACK PLAN
#============================================#

# If migration fails due to certificate issues:

# Step 1: RHEL rollback (using leapp or snapshots)
# See RHEL migration documentation

# Step 2: Restore certificates (if needed)
sudo tar xzf /var/backups/pre-migration-YYYYMMDD/certificates.tar.gz -C /

# Step 3: Restore service configs
sudo tar xzf /var/backups/pre-migration-YYYYMMDD/service-configs.tar.gz -C /

# Step 4: Restore certmonger
sudo tar xzf /var/backups/pre-migration-YYYYMMDD/certmonger.tar.gz -C /

# Step 5: Restart services
sudo systemctl restart httpd nginx postfix slapd

# Step 6: Verify
curl -v https://localhost/
sudo getcert list

34.8 Communication Plan

Stakeholder Communication Template

## RHEL Migration - Certificate Impact Assessment

### Migration Details
- **From:** RHEL X.Y
- **To:** RHEL X.Y
- **Date:** YYYY-MM-DD
- **Window:** XX:00 - XX:00 UTC

### Certificate Impact Analysis
- **Total Certificates:** XX
- **Certificates Requiring Action:** XX
- **Services Affected:** Apache, NGINX, Postfix, LDAP, etc.

### Pre-Migration Actions Required
- [ ] Reissue XX SHA-1 certificates
- [ ] Renew XX expiring certificates
- [ ] Update XX service configurations
- [ ] Test crypto-policy compatibility (RHEL 8+)

### During Migration
- **Expected Downtime:** X hours
- **Certificate Validation:** Post-migration
- **Rollback Plan:** Available if needed

### Post-Migration Validation
- [ ] All services start successfully
- [ ] Certificate validation working
- [ ] crypto-policy applied (RHEL 8+)
- [ ] certmonger tracking maintained
- [ ] Client connections successful

### Risk Mitigation
- Full backups completed
- Test migration successful
- Rollback procedure documented
- Team on standby

### Contact
- **Migration Lead:** Name <email>
- **Escalation:** Manager <email>

34.9 Migration Checklist

Complete Pre-Migration Checklist

## Certificate Migration Readiness Checklist

### Audit & Inventory (Week 1-3)
- [ ] Complete certificate inventory
- [ ] Document all certificate locations
- [ ] Identify all services using certificates
- [ ] Map certificate to service dependencies
- [ ] Document custom CAs in use

### Issue Identification (Week 2-4)
- [ ] Identify SHA-1 signed certificates
- [ ] Identify small keys (< 2048 bits)
- [ ] Identify certificates without SANs
- [ ] Identify expiring certificates (< 180 days)
- [ ] Identify hard-coded TLS configs (vs crypto-policy)

### Remediation (Week 3-6)
- [ ] Reissue all SHA-1 certificates
- [ ] Reissue small key certificates
- [ ] Add SANs to all certificates
- [ ] Renew expiring certificates
- [ ] Remove hard-coded TLS configs (prepare for crypto-policy)

### Testing (Week 5-7)
- [ ] Set up test environment
- [ ] Clone production certificates to test
- [ ] Perform test migration
- [ ] Validate all services start
- [ ] Test client connections
- [ ] Test crypto-policy (RHEL 7→8/9)
- [ ] Document issues found
- [ ] Resolve issues in test
- [ ] Re-test until clean

### Backup (Week 7)
- [ ] Full system backup
- [ ] Certificate-specific backup
- [ ] Service configuration backup
- [ ] certmonger database backup
- [ ] Test restore procedure

### Documentation (Week 7)
- [ ] Migration runbook complete
- [ ] Rollback procedure documented
- [ ] Issue workarounds documented
- [ ] Team briefed
- [ ] Stakeholders notified

### Final Prep (Day before)
- [ ] Verify backups
- [ ] Verify test environment
- [ ] Review runbook
- [ ] Confirm maintenance window
- [ ] Team roles assigned

34.10 Key Takeaways

  1. Plan ahead - Start 6-8 weeks before migration
  2. Audit thoroughly - Know every certificate
  3. Fix issues early - Don’t wait until migration day
  4. Test extensively - Multiple test runs
  5. Backup everything - Certificates, configs, certmonger DB
  6. Document clearly - Runbook, rollback, issues
  7. Communicate proactively - Keep stakeholders informed

Quick Reference Card

┌──────────────────────────────────────────────────────────────┐
│ MIGRATION PLANNING QUICK REFERENCE                           │
├──────────────────────────────────────────────────────────────┤
│ Timeline:     6-8 weeks before migration                     │
│                                                              │
│ Pre-audit:    Find all certificates                          │
│               Check signatures (SHA-1 → SHA-256)             │
│               Check key sizes (< 2048 → 2048+)               │
│               Check SANs (missing → add)                     │
│               Check expiration (< 180 days → renew)          │
│                                                              │
│ Backup:       tar czf certs.tar.gz /etc/pki/tls/             │
│               getcert list > certmonger-list.txt             │
│               update-crypto-policies --show > policy.txt     │
│                                                              │
│ Test:         Clone to test environment                      │
│               Perform test migration                         │
│               Validate certificates work                     │
│               Document and fix issues                        │
└──────────────────────────────────────────────────────────────┘

⚠️ SHA-1 certificates WILL FAIL on RHEL 9+
⚠️ crypto-policies introduced in RHEL 8
✅ Test multiple times before production

Chapter Navigation

Chapter 35: RHEL 7→8 Migration

Big Jump: Migrating from RHEL 7 to RHEL 8 introduces crypto-policies - a revolutionary change in certificate management. Plan carefully!


35.1 Certificate Impact: MODERATE-HIGH

What Changes

FeatureRHEL 7RHEL 8Impact
OpenSSL1.0.2k1.1.1kModerate
TLS Versions1.0/1.1/1.21.2/1.3 (DEFAULT)HIGH
Crypto-PoliciesNoneNEW!HIGH
Default CiphersMixedStricterModerate
certmongerBasicEnhancedLow
ManagementManualAutomated (crypto-policies)HIGH

Key Change: crypto-policies revolutionizes TLS management!


35.2 Pre-Migration Preparation

Certificate-Specific Pre-Migration Tasks

#============================================#
# RHEL 7→8 CERTIFICATE PREPARATION
#============================================#

# Task 1: Audit all certificates (see Ch 34)
./pre-migration-cert-audit.sh > rhel7-cert-audit.txt

# Task 2: Check for TLS 1.0/1.1 dependencies
# Review service configs
grep -r "TLSv1\|TLSv1.1" /etc/httpd/ /etc/nginx/ /etc/postfix/

# Task 3: Identify manual cipher configurations
# These will be overridden by crypto-policies!
grep -r "SSLCipherSuite\|ssl_ciphers\|smtp.*ciphers" /etc/httpd/ /etc/nginx/ /etc/postfix/

# Task 4: Test TLS 1.2 compatibility
# Ensure all clients support TLS 1.2+

# Task 5: Backup everything
sudo tar czf rhel7-complete-backup-$(date +%Y%m%d).tar.gz \
  /etc/pki/ \
  /etc/httpd/ \
  /etc/nginx/ \
  /etc/postfix/ \
  /var/lib/certmonger/

35.3 Migration Using leapp

The leapp Utility

IMPORTANT: Use leapp for RHEL 7→8 migration (NOT redhat-upgrade-tool!)

leapp is Red Hat’s supported upgrade utility for RHEL 7→8 and 8→9.

#============================================#
# RHEL 7→8 MIGRATION WITH LEAPP
#============================================#

# Prerequisites
# - RHEL 7.9 (latest)
# - Valid Red Hat subscription
# - All updates applied
# - Backups complete

# Step 1: Update RHEL 7 to latest
sudo yum update -y
sudo reboot

# Step 2: Install leapp
sudo yum install leapp-upgrade -y

# Step 3: Run pre-upgrade check
sudo leapp preupgrade

# Review report:
cat /var/log/leapp/leapp-report.txt

# Common certificate-related inhibitors:
# - SHA-1 certificates
# - Weak cipher configurations
# - Unsupported packages

# Step 4: Fix issues identified
# Reissue SHA-1 certificates
# Update configurations

# Step 5: Perform upgrade
sudo leapp upgrade

# System downloads RHEL 8, prepares upgrade
# Reboots automatically

# Step 6: After reboot, system is RHEL 8!
cat /etc/redhat-release
# Red Hat Enterprise Linux release 8.X (Ootpa)

35.4 Post-Migration Certificate Validation

Immediate Post-Migration Checks

#============================================#
# POST-MIGRATION CERTIFICATE VALIDATION
#============================================#

# Check 1: Verify RHEL 8
cat /etc/redhat-release
openssl version
# Should show: OpenSSL 1.1.1k

# Check 2: Verify crypto-policy
update-crypto-policies --show
# DEFAULT (should be set automatically)

# Check 3: Check certificate files still present
ls -la /etc/pki/tls/certs/
ls -la /etc/pki/tls/private/

# Check 4: Verify permissions unchanged
ls -l /etc/pki/tls/private/*.key
# Should still be 600

# Check 5: Check custom CAs
ls -la /etc/pki/ca-trust/source/anchors/

# Check 6: Update trust store (just in case)
sudo update-ca-trust

# Check 7: Verify certmonger tracking
sudo getcert list
# All certificates should still be tracked

# Check 8: Check service configurations
# crypto-policies may have updated them
cat /etc/crypto-policies/back-ends/httpd.config

35.5 Service Restart and Testing

Restart All Services

#============================================#
# RESTART SERVICES AFTER MIGRATION
#============================================#

# Restart certificate-using services
sudo systemctl restart httpd
sudo systemctl restart nginx
sudo systemctl restart postfix
sudo systemctl restart slapd
sudo systemctl restart postgresql
sudo systemctl restart mariadb

# Check service status
systemctl status httpd nginx postfix | grep "Active:"

# Test each service
curl -v https://localhost/                             # Apache/NGINX
openssl s_client -connect localhost:443                # HTTPS
openssl s_client -starttls smtp -connect localhost:25  # Postfix
openssl s_client -connect localhost:636                # LDAPS

35.6 Common RHEL 7→8 Certificate Issues

Issue 1: TLS 1.0/1.1 Clients Can’t Connect

Symptom: Old clients fail after migration

Cause: DEFAULT crypto-policy blocks TLS 1.0/1.1

Quick Fix (Temporary):

sudo update-crypto-policies --set LEGACY
sudo systemctl restart httpd nginx postfix

Proper Fix:

# Update clients to support TLS 1.2+
# Or create custom policy module

Issue 2: Hard-Coded Ciphers Conflict with crypto-policy

Symptom: Service won’t start or behaves unexpectedly

Cause: Old config has SSLCipherSuite that conflicts

Fix:

# Remove hard-coded cipher configs
# Let crypto-policy handle it

# Apache: Remove from ssl.conf
# SSLProtocol ...
# SSLCipherSuite ...

# NGINX: Remove from nginx.conf
# ssl_protocols ...
# ssl_ciphers ...

# Postfix: Remove from main.cf
# smtpd_tls_protocols ...
# smtpd_tls_mandatory_ciphers ...

Issue 3: certmonger Tracking Lost

Symptom: getcert list shows empty or missing certificates

Rare but possible if migration had issues

Fix:

# Restore certmonger database from backup
sudo systemctl stop certmonger
sudo tar xzf /var/backups/pre-migration-*/certmonger.tar.gz -C /
sudo systemctl start certmonger

# Verify
sudo getcert list

35.7 Crypto-Policy Transition

Adopting Crypto-Policies

RHEL 7: No crypto-policies, manual config per service RHEL 8: crypto-policies manage TLS system-wide

#============================================#
# TRANSITION TO CRYPTO-POLICIES
#============================================#

# After migration to RHEL 8:

# Step 1: Check current policy
update-crypto-policies --show
# DEFAULT

# Step 2: Remove manual TLS configs from services
# (Let crypto-policy handle it)

# Step 3: Test with DEFAULT policy
sudo systemctl restart httpd nginx postfix

# Step 4: If old clients need TLS 1.0/1.1 (temporary!)
sudo update-crypto-policies --set LEGACY

# Step 5: Plan to move back to DEFAULT
# Update clients, then:
sudo update-crypto-policies --set DEFAULT

35.8 Migration Runbook Example

Step-by-Step Execution

## RHEL 7→8 Migration Runbook - Certificate Section

### Pre-Migration (T-24 hours)
- [ ] Verify backups complete and tested
- [ ] Verify all certificates valid > 90 days
- [ ] No SHA-1 certificates remaining
- [ ] Test environment migration successful

### Migration Window Start (T=0)
- [ ] Announce maintenance window
- [ ] Take final backup
- [ ] Run: `sudo leapp upgrade`
- [ ] System reboots automatically

### Post-Reboot (T+30 min)
- [ ] Verify RHEL 8: `cat /etc/redhat-release`
- [ ] Check crypto-policy: `update-crypto-policies --show`
- [ ] Verify certificates present: `ls /etc/pki/tls/certs/`
- [ ] Check certmonger: `sudo getcert list`

### Service Validation (T+45 min)
- [ ] Restart all services
- [ ] Test Apache: `curl -v https://localhost/`
- [ ] Test NGINX: `curl -v https://localhost:8443/`
- [ ] Test Postfix: `openssl s_client -starttls smtp -connect localhost:25`
- [ ] Test LDAP: `ldapsearch -H ldaps://localhost:636 -x -b ""`
- [ ] Test databases (if applicable)

### Client Testing (T+60 min)
- [ ] Test from Windows clients
- [ ] Test from Linux clients
- [ ] Test from application clients
- [ ] Verify no TLS errors

### Monitoring (T+2 hours to T+48 hours)
- [ ] Monitor logs for certificate errors
- [ ] Monitor service health
- [ ] Check certmonger renewals
- [ ] Verify no crypto-policy issues

### Completion
- [ ] Document any issues encountered
- [ ] Update runbook with lessons learned
- [ ] Close maintenance window
- [ ] Notify stakeholders of successful migration

35.9 Key Takeaways

  1. Use leapp utility for RHEL 7→8 migration (supported method)
  2. crypto-policies are NEW in RHEL 8 - Major change!
  3. TLS 1.0/1.1 disabled by default - Test client compatibility
  4. Remove manual TLS configs - Let crypto-policy manage
  5. Test extensively before production migration
  6. LEGACY policy available for compatibility (temporary!)
  7. certmonger tracking should survive migration

Quick Reference Card

┌──────────────────────────────────────────────────────────────┐
│ RHEL 7→8 MIGRATION CERTIFICATE CHECKLIST                     │
├──────────────────────────────────────────────────────────────┤
│ Before:       Audit all certificates                         │
│               Reissue SHA-1 certificates                     │
│               Test TLS 1.2 compatibility                     │
│               Backup everything                              │
│                                                              │
│ Migration:    Use leapp upgrade (NOT redhat-upgrade-tool!)   │
│               System reboots automatically                   │
│                                                              │
│ After:        Verify RHEL 8                                  │
│               Check crypto-policy (DEFAULT)                  │
│               Restart all services                           │
│               Test client connections                        │
│               Monitor for 48 hours                           │
│                                                              │
│ New Feature:  crypto-policies (system-wide TLS control)      │
│ Blocked:      TLS 1.0/1.1 (in DEFAULT policy)                │
│ Fallback:     LEGACY policy (if needed, temporary!)          │
└──────────────────────────────────────────────────────────────┘

✅ Use leapp (officially supported)
⚠️ Major change: crypto-policies introduced
⚠️ Test client TLS 1.2 support before migration

🧪 Hands-On Lab

Lab 17: RHEL 7→8 Migration

Migrate certificates during OS upgrade to RHEL 8

  • 📁 Location: labs/en_US/17-rhel7to8-migration/
  • ⏱️ Time: 40-50 minutes
  • 🎯 Level: Advanced

Chapter Navigation

Chapter 36: RHEL 8→9 Migration

OpenSSL 3.x Transition: RHEL 8→9 brings OpenSSL 3.x with provider architecture and stricter validation. Plan carefully for this significant change.


36.1 Certificate Impact: HIGH

What Changes

FeatureRHEL 8RHEL 9Impact
OpenSSL1.1.1k3.5.5HIGH
ArchitectureTraditionalProvider-basedHIGH
TLS 1.0/1.1LEGACY policyCompletely removedHIGH
SHA-1DeprecatedBlockedHIGH
ValidationStandardStricterModerate
Crypto-PoliciesBasicSubpoliciesLow
certmongerEnhancedStill native for IPA/trackingLow

Key Change: OpenSSL 3.x is a major architectural change!


36.2 Pre-Migration Requirements

Critical Certificate Fixes

Requirement 1: NO SHA-1 Signatures

#============================================#
# CHECK FOR SHA-1 (WILL FAIL ON RHEL 9!)
#============================================#

# Find SHA-1 signed certificates
for cert in /etc/pki/tls/certs/*.crt; do
  SIG=$(openssl x509 -in "$cert" -noout -text 2>/dev/null | \
        grep "Signature Algorithm" | head -2)
  if echo "$SIG" | grep -qi "sha1"; then
    echo "🚨 CRITICAL: SHA-1 signature: $cert"
    echo "   $SIG"
    echo "   ⚠️ MUST reissue before RHEL 9 migration!"
  fi
done

# Action: Reissue ALL SHA-1 certificates before migration
# No exceptions - they WILL fail on RHEL 9

Requirement 2: All Certificates Valid

# Ensure no expired certificates
for cert in /etc/pki/tls/certs/*.crt; do
  if ! openssl x509 -in "$cert" -noout -checkend 0 2>/dev/null; then
    echo "❌ Expired: $cert"
  fi
done

Requirement 3: Test Custom Applications

# If you have custom applications using OpenSSL
# They may need updates for OpenSSL 3.x API
rpm -qa | grep -E "custom|local"

# Test these applications in RHEL 9 environment before migration

36.3 Migration Using leapp

RHEL 8→9 Upgrade Process

#============================================#
# RHEL 8→9 MIGRATION WITH LEAPP
#============================================#

# Prerequisites
# - RHEL 8.10 (latest recommended)
# - Valid subscription
# - All updates applied
# - Backups complete
# - SHA-1 certificates reissued!

# Step 1: Update RHEL 8 fully
sudo dnf update -y
sudo reboot

# Step 2: Install leapp
sudo dnf install leapp-upgrade -y

# Step 3: Run pre-upgrade check
sudo leapp preupgrade

# Review report
cat /var/log/leapp/leapp-report.txt

# Certificate-related checks:
# - SHA-1 certificate warnings
# - OpenSSL compatibility
# - Custom app compatibility

# Step 4: Address inhibitors
# Fix any blocking issues

# Step 5: Perform upgrade
sudo leapp upgrade

# Downloads RHEL 9, prepares upgrade
# Reboots to perform upgrade
# Reboots again into RHEL 9

# Step 6: Verify RHEL 9
cat /etc/redhat-release
# Red Hat Enterprise Linux release 9.X (Plow)

openssl version
# OpenSSL 3.5.5

36.4 Post-Migration Validation

Certificate-Specific Validation

#============================================#
# POST-MIGRATION CERTIFICATE VALIDATION (RHEL 9)
#============================================#

# Check 1: OpenSSL version
openssl version
# OpenSSL 3.5.5  ← Confirm

# Check 2: Check providers
openssl list -providers
# Should show: default, fips, legacy, base

# Check 3: Verify certificates still present
ls -la /etc/pki/tls/certs/
ls -la /etc/pki/tls/private/

# Check 4: Test certificate validation
for cert in /etc/pki/tls/certs/*.crt; do
  openssl verify "$cert" 2>&1 | grep -v "OK" && echo "Issue: $cert"
done

# Check 5: Verify crypto-policy
update-crypto-policies --show
# DEFAULT (should be maintained)

# Check 6: Test certificate operations
openssl x509 -in /etc/pki/tls/certs/server.crt -noout -text

# Check 7: Verify certmonger tracking
sudo getcert list
# All certificates should still be tracked

# Check 8: Check trust store
trust list | head -20

36.5 Service Validation

Test All Services

#============================================#
# SERVICE VALIDATION POST-MIGRATION
#============================================#

# Restart services
sudo systemctl restart httpd nginx postfix slapd postgresql mariadb 2>/dev/null

# Test each service
echo "Testing Apache..."
curl -v https://localhost/ 2>&1 | grep -E "(SSL connection|subject:)"

echo "Testing with OpenSSL 3.x..."
openssl s_client -connect localhost:443 -tls1_3

echo "Testing Postfix..."
openssl s_client -starttls smtp -connect localhost:25 </dev/null

echo "Testing LDAPS..."
openssl s_client -connect localhost:636 </dev/null

# Check for provider errors
sudo journalctl --since "1 hour ago" | grep -i "provider\|unsupported"

36.6 Common RHEL 8→9 Issues

Issue 1: SHA-1 Certificates Rejected

Symptom:

openssl verify server.crt
# error 3 at 0 depth lookup: CA md too weak

Cause: Certificate has SHA-1 signature (blocked on RHEL 9)

Solution:

# NO WORKAROUND - Must reissue
# This should have been done pre-migration!

# Emergency: Reissue immediately
openssl req -new -key server.key -out server.csr -sha256
# Submit to CA, install new certificate

Issue 2: Legacy Algorithm Errors

Symptom:

openssl md5 file.txt
# Error: unsupported

Cause: MD5 and other legacy algorithms require explicit provider

Solution:

# Use legacy provider
openssl md5 -provider legacy file.txt

# Better: Update to use SHA-256
openssl sha256 file.txt

Issue 3: Custom Application OpenSSL 3.x Incompatibility

Symptom: Custom application fails with OpenSSL errors

Cause: Application compiled against OpenSSL 1.1.1, API changed in 3.x

Solution:

# Recompile application against OpenSSL 3.x
# Or update application code for new API

# Temporary workaround (if available):
# Use compat library (if provided)

36.7 crypto-policy Considerations

Crypto-Policy After Migration

#============================================#
# CRYPTO-POLICY POST-MIGRATION
#============================================#

# Check current policy (should be maintained)
update-crypto-policies --show

# RHEL 9 supports subpolicies!
# Example: Completely disable SHA-1
sudo update-crypto-policies --set DEFAULT:NO-SHA1

# List available modules
ls /usr/share/crypto-policies/policies/modules/

# Test policy
sudo systemctl restart httpd
curl -v https://localhost/

36.8 certmonger After Migration

Verify certmonger Functionality

#============================================#
# CERTMONGER POST-MIGRATION
#============================================#

# Check certmonger status
systemctl status certmonger

# List tracked certificates
sudo getcert list

# Check for issues
sudo getcert list | grep "status:" | grep -v "MONITORING"

# If using FreeIPA, test connectivity
ipa ping

# Force test renewal
sudo ipa-getcert resubmit -f /etc/pki/tls/certs/test.crt

# RHEL 9 still uses certmonger for IPA, local CA, and tracking workflows
# Use certbot separately for public Let's Encrypt certificates

36.9 Migration Runbook

Certificate-Focused Runbook

## RHEL 8→9 Migration - Certificate Section

### Pre-Migration (T-24 hours)
- [ ] Verify NO SHA-1 certificates (critical!)
- [ ] All certificates valid > 90 days
- [ ] Backups complete and tested
- [ ] Test migration successful
- [ ] Custom apps tested on RHEL 9

### Migration Window Start (T=0)
- [ ] Final backup
- [ ] Run: `sudo leapp upgrade`
- [ ] System reboots (twice)

### Post-Reboot Validation (T+45 min)
- [ ] Verify RHEL 9: `cat /etc/redhat-release`
- [ ] Verify OpenSSL 3.5.5: `openssl version`
- [ ] Check providers: `openssl list -providers`
- [ ] Verify certificates: `ls /etc/pki/tls/certs/`
- [ ] Check crypto-policy: `update-crypto-policies --show`
- [ ] Verify certmonger: `sudo getcert list`

### Service Restart (T+60 min)
- [ ] Restart all certificate-using services
- [ ] Test Apache/NGINX
- [ ] Test Postfix
- [ ] Test LDAP
- [ ] Test databases

### Certificate Validation (T+90 min)
- [ ] No SHA-1 rejections
- [ ] All certificates validate: `openssl verify`
- [ ] TLS 1.3 working: `openssl s_client -tls1_3`
- [ ] No provider errors in logs
- [ ] certmonger status all MONITORING

### Client Testing (T+2 hours)
- [ ] Test from all client types
- [ ] Verify no compatibility issues
- [ ] Check application functionality

### Post-Migration (24-48 hours)
- [ ] Monitor for OpenSSL 3.x issues
- [ ] Check certmonger renewals
- [ ] Monitor service logs
- [ ] Document any issues

36.10 Key Takeaways

  1. OpenSSL 3.x is major change - Provider architecture is new
  2. SHA-1 MUST be eliminated before migration - No exceptions!
  3. Use leapp for migration (officially supported)
  4. Test custom applications on RHEL 9 first
  5. Stricter validation catches more issues (good for security!)
  6. certmonger remains the native tracker for IPA and managed renewals on RHEL 9
  7. Subpolicies available for fine-tuning

Quick Reference Card

┌──────────────────────────────────────────────────────────────┐
│ RHEL 8→9 MIGRATION CERTIFICATE CHECKLIST                     │
├──────────────────────────────────────────────────────────────┤
│ CRITICAL:   NO SHA-1 certificates! (will be rejected)        │
│             Reissue all SHA-1 certs before migration         │
│                                                              │
│ Before:     Verify NO SHA-1 signatures                       │
│             Test custom apps on RHEL 9                       │
│             Backup everything                                │
│                                                              │
│ Migration:  Use leapp upgrade                                │
│             System reboots twice                             │
│                                                              │
│ After:      Verify OpenSSL 3.5.5                             │
│             Check providers: openssl list -providers         │
│             Test legacy algorithms need -provider legacy     │
│             Restart all services                             │
│             Verify certmonger tracking maintained            │
│                                                              │
│ New:        Provider architecture                            │
│             Subpolicies (DEFAULT:NO-SHA1)                    │
│             certmonger tracking still intact                 │
└──────────────────────────────────────────────────────────────┘

🚨 SHA-1 is BLOCKED - reissue before migration!
✅ OpenSSL 3.x brings stricter security
✅ Use certmonger for IPA/tracked renewals; certbot for public ACME

🧪 Hands-On Lab

Lab 18: RHEL 8→9 Migration

Handle OpenSSL 3.x and stricter security in RHEL 9

  • 📁 Location: labs/en_US/18-rhel8to9-migration/
  • ⏱️ Time: 40-50 minutes
  • 🎯 Level: Advanced

Chapter Navigation

Chapter 37: Migration Troubleshooting & Recovery

When Things Go Wrong: Migrations don’t always go smoothly. This chapter covers common migration problems and recovery procedures.


37.1 Common Migration Issues

Top 10 Certificate Migration Problems

ProblemSymptomsCauseQuick Fix
1. Services won’t startsystemctl status failsConfig syntax changedRestore config, update syntax
2. SHA-1 rejection (RHEL 9)“ca md too weak”SHA-1 signatureReissue certificate
3. TLS version mismatchClients can’t connectTLS 1.0/1.1 blockedLEGACY policy (temp)
4. crypto-policy issuesVarious errorsNew policy systemUnderstand & configure
5. certmonger lost trackinggetcert list emptyDB corruptionRestore from backup
6. Missing CAsCert verify failedTrust store resetRe-add CAs
7. Permission changesPermission deniedOwnership changedFix permissions
8. SELinux denialsService blockedContext changedRelabel files
9. Provider errors (RHEL 9)Algorithm unsupportedOpenSSL 3.x changeUse -provider legacy
10. Performance degradationSlow connectionsStricter cryptoExpected, or tune

37.2 Rollback Procedures

When to Rollback

Rollback if:

  • Critical services can’t start
  • Certificate issues can’t be quickly fixed
  • Business impact is severe
  • Within rollback window (usually 24-48 hours)

leapp Rollback

#============================================#
# ROLLBACK RHEL MIGRATION
#============================================#

# leapp creates snapshot during upgrade
# Rollback BEFORE rebooting into new version

# During upgrade (if issues detected):
# Don't reboot - investigate and fix

# After upgrade but issues found:
# Check if within rollback window

# leapp doesn't have automatic rollback
# Use snapshot/backup to restore

# With LVM snapshot (if created pre-migration):
# Boot from snapshot
# Or restore from backup

Certificate-Specific Rollback

#============================================#
# RESTORE CERTIFICATES AFTER FAILED MIGRATION
#============================================#

# Scenario: Migrated, but certificate issues
# Need to restore certificate state

# Step 1: Stop services
sudo systemctl stop httpd nginx postfix slapd

# Step 2: Restore certificates
sudo tar xzf /var/backups/pre-migration-*/certificates.tar.gz -C /

# Step 3: Restore service configs
sudo tar xzf /var/backups/pre-migration-*/service-configs.tar.gz -C /

# Step 4: Restore certmonger
sudo systemctl stop certmonger
sudo tar xzf /var/backups/pre-migration-*/certmonger.tar.gz -C /
sudo systemctl start certmonger

# Step 5: Restore crypto-policy (if RHEL 8+)
POLICY=$(cat /var/backups/pre-migration-*/crypto-policy.txt)
sudo update-crypto-policies --set $POLICY

# Step 6: Start services
sudo systemctl start httpd nginx postfix slapd

# Step 7: Verify
curl -v https://localhost/
sudo getcert list

37.3 Service Won’t Start After Migration

Diagnosis

#============================================#
# SERVICE STARTUP TROUBLESHOOTING
#============================================#

# Check service status
systemctl status httpd

# View detailed errors
sudo journalctl -xe -u httpd

# Test configuration
# Apache:
sudo apachectl configtest

# NGINX:
sudo nginx -t

# Postfix:
sudo postfix check

# Common certificate-related errors:
# - File not found
# - Permission denied
# - Certificate format invalid
# - ca md too weak (SHA-1)

Solutions

Problem: Configuration Syntax Changed

# Some directives changed between versions
# Check release notes for changes

# Temporarily restore old config
sudo cp /var/backups/pre-migration-*/ssl.conf /etc/httpd/conf.d/

# Update to new syntax
# Research correct syntax for new version

Problem: Permission Changed During Migration

# Fix permissions
sudo chmod 600 /etc/pki/tls/private/*.key
sudo chmod 644 /etc/pki/tls/certs/*.crt

# Fix ownership
sudo chown root:root /etc/pki/tls/private/*.key

# Fix SELinux contexts
sudo restorecon -Rv /etc/pki/tls/

37.4 Client Connection Failures Post-Migration

TLS Version Incompatibility

Symptom: Clients can’t connect after migration to RHEL 8/9

Diagnosis:

# Test from server
openssl s_client -connect localhost:443 -tls1_2
# Works

openssl s_client -connect localhost:443 -tls1
# Fails (expected on RHEL 8/9 DEFAULT)

# Check crypto-policy
update-crypto-policies --show
# DEFAULT  ← Blocks TLS 1.0/1.1

Temporary Solution:

# Allow TLS 1.0/1.1 temporarily
sudo update-crypto-policies --set LEGACY
sudo systemctl restart httpd nginx postfix

# Test clients
# Document which clients need TLS 1.0/1.1

# Plan to update those clients, then revert to DEFAULT

Proper Solution:

# Update clients to support TLS 1.2+
# Then use DEFAULT policy

sudo update-crypto-policies --set DEFAULT

37.5 certmonger Issues Post-Migration

certmonger Tracking Lost

Symptom:

sudo getcert list
# (empty or missing certificates)

Solution:

# Restore certmonger database
sudo systemctl stop certmonger
sudo tar xzf /var/backups/pre-migration-*/certmonger.tar.gz -C /
sudo systemctl start certmonger

# Verify
sudo getcert list

# If still issues, re-add certificates manually

certmonger CA_UNREACHABLE After Migration

Common after RHEL upgrade

Solution:

# Renew Kerberos ticket
sudo kinit -k host/$(hostname -f)@REALM

# Restart certmonger
sudo systemctl restart certmonger

# Resubmit requests
for cert in $(sudo getcert list | grep "certificate:" | sed -n "s/.*location='\\([^']*\\)'.*/\\1/p"); do
  sudo ipa-getcert resubmit -f "$cert"
done

37.6 Emergency Recovery Procedures

Emergency: All Services Down

Situation: Migration complete but nothing works

Quick Recovery:

#!/bin/bash
# emergency-post-migration-recovery.sh

echo "=== EMERGENCY: Post-Migration Certificate Recovery ==="

# 1. Check RHEL version (confirm migration happened)
cat /etc/redhat-release

# 2. Emergency: Disable SSL temporarily
# Apache
sudo mv /etc/httpd/conf.d/ssl.conf /etc/httpd/conf.d/ssl.conf.disabled
sudo systemctl start httpd
# Now Apache runs on HTTP only (port 80)

# 3. Identify certificate issues
sudo journalctl -xe | grep -i cert | tail -50

# 4. For RHEL 9: Check for SHA-1 rejections
grep "ca md too weak" /var/log/messages

# 5. Generate temporary self-signed certificates
/usr/local/bin/emergency-self-signed-cert.sh $(hostname -f) 90

# 6. Re-enable SSL with temp cert
sudo mv /etc/httpd/conf.d/ssl.conf.disabled /etc/httpd/conf.d/ssl.conf
# Update to use temp cert
sudo systemctl restart httpd

# 7. Services restored (with warnings)
# Plan proper certificate fixes

echo "✅ Emergency recovery complete"
echo "⚠️ Using temporary certificates - fix ASAP!"

37.7 Post-Migration Validation Script

Comprehensive Validation

#!/bin/bash
# post-migration-cert-validation.sh

echo "=== Post-Migration Certificate Validation ==="

ISSUES=0

# Check RHEL version
echo "1. RHEL Version:"
cat /etc/redhat-release

# Check OpenSSL
echo ""
echo "2. OpenSSL Version:"
openssl version

# Check crypto-policy (RHEL 8+)
if command -v update-crypto-policies &>/dev/null; then
  echo ""
  echo "3. Crypto-Policy:"
  update-crypto-policies --show
fi

# Check certificates
echo ""
echo "4. Certificate Status:"
CERT_COUNT=0
EXPIRED=0
for cert in /etc/pki/tls/certs/*.crt; do
  [ -f "$cert" ] || continue
  ((CERT_COUNT++))

  if ! openssl x509 -in "$cert" -noout -checkend 0 2>/dev/null; then
    echo "  ❌ EXPIRED: $cert"
    ((EXPIRED++))
    ((ISSUES++))
  fi

  # Check for SHA-1 (RHEL 9)
  if [ "$(cat /etc/redhat-release)" =~ "release 9" ]; then
    if openssl x509 -in "$cert" -noout -text | grep -qi "sha1.*Signature"; then
      echo "  ❌ SHA-1: $cert"
      ((ISSUES++))
    fi
  fi
done

echo "  Total certificates: $CERT_COUNT"
echo "  Expired: $EXPIRED"

# Check certmonger
echo ""
echo "5. certmonger Status:"
if command -v getcert &>/dev/null; then
  sudo getcert list | grep "status:" | sort | uniq -c

  UNREACHABLE=$(sudo getcert list | grep -c "CA_UNREACHABLE")
  if [ $UNREACHABLE -gt 0 ]; then
    echo "  ⚠️ $UNREACHABLE certificates CA_UNREACHABLE"
    ((ISSUES++))
  fi
else
  echo "  certmonger not installed"
fi

# Check services
echo ""
echo "6. Service Status:"
for svc in httpd nginx postfix slapd; do
  if systemctl is-active --quiet $svc 2>/dev/null; then
    echo "  ✅ $svc: running"
  elif systemctl is-enabled --quiet $svc 2>/dev/null; then
    echo "  ❌ $svc: not running (should be)"
    ((ISSUES++))
  fi
done

# Test connections
echo ""
echo "7. Connection Tests:"
timeout 3 curl -ks https://localhost/ &>/dev/null && \
  echo "  ✅ HTTPS: OK" || echo "  ❌ HTTPS: FAILED"

# Summary
echo ""
echo "==================================="
if [ $ISSUES -eq 0 ]; then
  echo "✅ Migration validation PASSED!"
  exit 0
else
  echo "⚠️ $ISSUES issues found - review above"
  exit 1
fi

37.8 Key Takeaways

  1. Have rollback plan ready before migration
  2. Most issues are fixable without rollback
  3. Crypto-policy changes cause most compatibility issues
  4. SHA-1 rejection is non-negotiable on RHEL 9
  5. Test, test, test before production migration
  6. Document everything during troubleshooting
  7. Emergency procedures (Ch 33) apply during migration too

Quick Reference Card

┌──────────────────────────────────────────────────────────────┐
│ MIGRATION TROUBLESHOOTING QUICK REFERENCE                    │
├──────────────────────────────────────────────────────────────┤
│ Service fails:  Check: journalctl -xe -u <service>           │
│                 Try: Restore config from backup              │
│                                                              │
│ Cert rejected:  Check: Signature algorithm (SHA-1?)          │
│                 Fix: Reissue with SHA-256+                   │
│                                                              │
│ Client fails:   Check: TLS version support                   │
│                 Temp: update-crypto-policies --set LEGACY    │
│                 Fix: Update client                           │
│                                                              │
│ certmonger:     Check: getcert list                          │
│                 Fix: Restore /var/lib/certmonger/            │
│                                                              │
│ Emergency:      Disable SSL temporarily                      │
│                 Generate temp self-signed                    │
│                 Restore from backup                          │
│                                                              │
│ Rollback:       Use snapshot/backup                          │
│                 Restore certificates                         │
│                 Restore configs                              │
└──────────────────────────────────────────────────────────────┘

✅ Most issues are fixable without full rollback
⚠️ Have backups ready
⚠️ Test in non-production first

Chapter Navigation

Chapter 38: FIPS Mode Complete Guide

Federal Compliance: FIPS 140-2/140-3 compliance is required for US federal systems and many regulated industries. Learn how to enable and manage FIPS mode on RHEL.


38.1 What is FIPS?

FIPS = Federal Information Processing Standards

FIPS 140-2/140-3 = Cryptographic Module Validation Program

Purpose:

  • ✅ Validate cryptographic modules meet security requirements
  • ✅ Ensure proper implementation of approved algorithms
  • ✅ Required for US federal government systems
  • ✅ Often required for: Banking, healthcare, defense contractors

FIPS Validation Status by RHEL Version

RHEL VersionFIPS StatusStandardNotes
RHEL 7ValidatedFIPS 140-2OpenSSL 1.0.2 modules validated
RHEL 8ValidatedFIPS 140-2OpenSSL 1.1.1, NSS, libgcrypt validated
RHEL 9ValidatedFIPS 140-2OpenSSL 3.x provider, transition to 140-3 in progress
RHEL 10In processFIPS 140-2/140-3Transition ongoing, check current status

Important: As of 2025, RHEL 9 uses FIPS 140-2 validated modules. The transition to FIPS 140-3 is in progress but not yet complete. Always verify current validation status at https://csrc.nist.gov/projects/cryptographic-module-validation-program


38.2 Enabling FIPS Mode

Best Practice: Enable FIPS during RHEL installation

# At installation boot prompt, add:
fips=1

# System installs in FIPS mode from the start
# All cryptographic operations are FIPS-compliant from boot

Why Installation-Time is Better:

  • Kernel properly configured
  • All packages installed in FIPS mode
  • No post-install migration needed
  • Cleaner FIPS state

Post-Installation FIPS (RHEL 8/9/10)

#============================================#
# ENABLE FIPS MODE POST-INSTALLATION
#============================================#

# Check current FIPS status
fips-mode-setup --check
# FIPS mode is disabled.

# Enable FIPS mode
sudo fips-mode-setup --enable

# Output shows what will change:
# - Kernel boot parameters
# - Crypto policy
# - System reconfiguration

# MUST REBOOT!
sudo reboot

# After reboot, verify
fips-mode-setup --check
# FIPS mode is enabled.

# Verify crypto-policy
update-crypto-policies --show
# FIPS

# Verify FIPS provider loaded (RHEL 9+)
openssl list -providers | grep fips
#   fips
#     name: OpenSSL FIPS Provider
#     version: 3.5.5
#     status: active

38.3 FIPS Requirements for Certificates

Approved Algorithms

FIPS-Approved for Certificates:

✅ RSA: 2048, 3072, 4096 bits
✅ ECC: P-256 (secp256r1), P-384 (secp384r1), P-521 (secp521r1)
✅ Signatures: SHA-256, SHA-384, SHA-512
✅ TLS: 1.2, 1.3 only

Blocked in FIPS Mode:

❌ RSA < 2048 bits
❌ MD5, SHA-1
❌ TLS 1.0, 1.1
❌ 3DES, RC4, DES
❌ DSA keys
❌ Non-approved elliptic curves

38.4 Generating FIPS-Compliant Certificates

FIPS-Compliant Key Generation

#============================================#
# GENERATE FIPS-COMPLIANT KEYS
#============================================#

# Verify FIPS mode enabled
fips-mode-setup --check

# Generate RSA 2048 key (FIPS-compliant)
openssl genpkey -algorithm RSA -out fips-server.key \
  -pkeyopt rsa_keygen_bits:2048

# RSA 3072 (stronger, still FIPS-compliant)
openssl genpkey -algorithm RSA -out fips-server.key \
  -pkeyopt rsa_keygen_bits:3072

# EC P-256 (FIPS-approved curve)
openssl genpkey -algorithm EC -out fips-ec.key \
  -pkeyopt ec_paramgen_curve:P-256

# EC P-384 (stronger, FIPS-approved)
openssl genpkey -algorithm EC -out fips-ec.key \
  -pkeyopt ec_paramgen_curve:P-384

# Verify key generated in FIPS mode
openssl pkey -in fips-server.key -check

FIPS-Compliant CSR

#============================================#
# GENERATE FIPS-COMPLIANT CSR
#============================================#

# CSR with SHA-256 (FIPS-approved)
openssl req -new -key fips-server.key -out fips-server.csr \
  -sha256 \
  -subj "/C=US/O=Federal Agency/CN=secure.example.gov" \
  -addext "subjectAltName=DNS:secure.example.gov"

# SHA-384 (stronger, FIPS-approved)
openssl req -new -key fips-server.key -out fips-server.csr \
  -sha384 \
  -subj "/C=US/O=Federal Agency/CN=secure.example.gov"

# ❌ NEVER use SHA-1 or MD5 in FIPS mode
# They will be rejected!

38.5 FIPS Mode Verification

Complete FIPS Verification

#============================================#
# VERIFY FIPS MODE IS ACTIVE
#============================================#

# Check 1: fips-mode-setup
fips-mode-setup --check
# FIPS mode is enabled.

# Check 2: Kernel parameter
cat /proc/cmdline | grep fips
# Should show: fips=1

# Check 3: Crypto-policy
update-crypto-policies --show
# FIPS

# Check 4: OpenSSL FIPS provider (RHEL 9+)
openssl list -providers
# Should show fips provider as active

# Check 5: Test FIPS-only operation
# Try non-FIPS algorithm (should fail)
echo "test" | openssl md5
# Error: disabled for FIPS  ← Good!

# Check 6: Verify certificate operations use FIPS
openssl version -a | grep FIPS

38.6 FIPS Crypto-Policy

Understanding FIPS Policy

#============================================#
# FIPS CRYPTO-POLICY DETAILS
#============================================#

# Policy is automatically set to FIPS when FIPS mode enabled
update-crypto-policies --show
# FIPS

# What FIPS policy enforces:
cat /etc/crypto-policies/back-ends/opensslcnf.config

# Key settings:
# - TLS 1.2 minimum
# - Only FIPS-approved ciphers
# - Only FIPS-approved signature algorithms
# - Minimum 2048-bit keys

Cannot change from FIPS policy while in FIPS mode!


38.7 Services in FIPS Mode

Apache in FIPS Mode

#============================================#
# APACHE IN FIPS MODE
#============================================#

# Apache automatically uses FIPS policy
# No manual configuration needed!

# Verify
sudo systemctl restart httpd

# Test
openssl s_client -connect localhost:443

# Should show:
# - TLS 1.2 or 1.3
# - FIPS-approved cipher
# - No weak algorithms

# View actual Apache FIPS config
cat /etc/crypto-policies/back-ends/httpd.config

Other Services

All services automatically comply with FIPS policy:

  • NGINX → Uses FIPS ciphers/protocols
  • Postfix → FIPS-compliant TLS
  • OpenSSH → FIPS algorithms only
  • Databases → FIPS-approved SSL

38.8 Common FIPS Issues

Issue 1: Non-FIPS Algorithm Attempted

Symptom:

Error: disabled for FIPS

Examples:

# MD5 (not FIPS-approved)
openssl md5 file.txt
# Error: digital envelope routines:EVP_DigestInit_ex:disabled for fips

# SHA-1 signature (not FIPS-approved for signing)
openssl dgst -sha1 -sign key.pem file.txt
# Error: disabled for fips

Solution:

# Use FIPS-approved algorithms
openssl sha256 file.txt  # Use SHA-256 instead of MD5
openssl dgst -sha256 -sign key.pem file.txt  # Use SHA-256 for signing

Issue 2: Legacy Application Incompatible

Symptom: Application fails in FIPS mode

Cause: Application uses non-FIPS algorithms (MD5, SHA-1, weak ciphers)

Solutions:

# Solution 1: Update application to use FIPS algorithms

# Solution 2: If application can't be updated:
# May not be able to run in FIPS mode
# Consider if FIPS is actually required

# Solution 3: Container isolation (advanced)
# Run non-FIPS app in container without FIPS

38.9 Disabling FIPS Mode

When and How to Disable

#============================================#
# DISABLE FIPS MODE (if needed)
#============================================#

# Check current status
fips-mode-setup --check

# Disable FIPS
sudo fips-mode-setup --disable

# MUST REBOOT
sudo reboot

# After reboot
fips-mode-setup --check
# FIPS mode is disabled.

# Crypto-policy reverts to DEFAULT
update-crypto-policies --show
# DEFAULT

Note: Disabling FIPS may have compliance implications!


38.10 Key Takeaways

  1. FIPS 140-2 is current standard on RHEL (140-3 transition in progress)
  2. Enable at installation for cleanest FIPS state
  3. Post-install enable requires reboot
  4. Only FIPS-approved algorithms allowed
  5. Crypto-policy automatically set to FIPS
  6. Services automatically comply
  7. Test applications before enabling FIPS in production

Quick Reference Card

┌──────────────────────────────────────────────────────────────┐
│ FIPS MODE QUICK REFERENCE                                    │
├──────────────────────────────────────────────────────────────┤
│ Status:       fips-mode-setup --check                        │
│ Enable:       sudo fips-mode-setup --enable && reboot        │
│ Disable:      sudo fips-mode-setup --disable && reboot       │
│                                                              │
│ Standard:     FIPS 140-2 (validated)                         │
│               FIPS 140-3 (transition in progress)            │
│                                                              │
│ Approved:     RSA 2048+, ECC P-256/384/521                   │
│               SHA-256/384/512                                │
│               TLS 1.2/1.3                                    │
│                                                              │
│ Blocked:      MD5, SHA-1, TLS 1.0/1.1                        │
│               RSA < 2048, 3DES, RC4                          │
│                                                              │
│ Policy:       Automatically set to FIPS                      │
│ Verify:       openssl list -providers | grep fips            │
└──────────────────────────────────────────────────────────────┘

⚠️ FIPS 140-2 is current (140-3 transition ongoing)
⚠️ Requires reboot to enable/disable
✅ All RHEL services automatically comply

🧪 Hands-On Lab

Lab 19: FIPS Mode Configuration

Enable and configure FIPS 140-2 compliance mode

  • 📁 Location: labs/en_US/19-fips-mode/
  • ⏱️ Time: 40-50 minutes
  • 🎯 Level: Advanced

Chapter Navigation

Chapter 39: FIPS-Compliant Certificates

Compliance Ready: Learn how to generate, validate, and manage FIPS-compliant certificates on RHEL for federal and regulated environments.


39.1 FIPS Certificate Requirements

Mandatory Requirements

For FIPS 140-2/140-3 Compliance:

✅ Key Algorithm: RSA 2048+ or ECC P-256/384/521
✅ Signature: SHA-256, SHA-384, or SHA-512
✅ TLS Protocols: 1.2 or 1.3 only
✅ Generated in FIPS mode (for new keys)
✅ Validated module used for operations

❌ NO MD5, SHA-1
❌ NO RSA < 2048 bits
❌ NO TLS 1.0/1.1
❌ NO 3DES, RC4, DES
❌ NO non-approved algorithms

39.2 Generating FIPS Certificates

Complete FIPS Certificate Workflow

#============================================#
# COMPLETE FIPS CERTIFICATE GENERATION
#============================================#

# Prerequisites: FIPS mode must be enabled
fips-mode-setup --check
# FIPS mode is enabled.

# Step 1: Generate FIPS-compliant RSA key
openssl genpkey -algorithm RSA \
  -out /etc/pki/tls/private/fips-server.key \
  -pkeyopt rsa_keygen_bits:2048

# Or stronger (3072/4096)
openssl genpkey -algorithm RSA \
  -out /etc/pki/tls/private/fips-server.key \
  -pkeyopt rsa_keygen_bits:3072

# Step 2: Set permissions
sudo chmod 600 /etc/pki/tls/private/fips-server.key

# Step 3: Generate CSR with SHA-256
openssl req -new \
  -key /etc/pki/tls/private/fips-server.key \
  -out /tmp/fips-server.csr \
  -sha256 \
  -subj "/C=US/O=Federal Agency/OU=IT/CN=secure.example.gov" \
  -addext "subjectAltName=DNS:secure.example.gov,DNS:www.secure.example.gov"

# Step 4: Verify CSR
openssl req -in /tmp/fips-server.csr -noout -text | grep -E "(Signature Algorithm|Public-Key)"
# Signature Algorithm: sha256WithRSAEncryption  ← Must be SHA-256+
# Public-Key: (2048 bit)  ← Must be 2048+

# Step 5: Submit to FIPS-compliant CA
# Get certificate back

# Step 6: Verify certificate compliance
openssl x509 -in fips-server.crt -noout -text | grep "Signature Algorithm"
# Signature Algorithm: sha256WithRSAEncryption  ← Good!

FIPS-Compliant EC Keys

#============================================#
# ELLIPTIC CURVE KEYS FOR FIPS
#============================================#

# P-256 (FIPS-approved)
openssl genpkey -algorithm EC \
  -out /etc/pki/tls/private/fips-ec.key \
  -pkeyopt ec_paramgen_curve:P-256

# P-384 (stronger, FIPS-approved)
openssl genpkey -algorithm EC \
  -out /etc/pki/tls/private/fips-ec.key \
  -pkeyopt ec_paramgen_curve:P-384

# Generate CSR
openssl req -new -key /etc/pki/tls/private/fips-ec.key \
  -out /tmp/fips-ec.csr \
  -sha256 \
  -subj "/CN=secure.example.gov"

39.3 Validating FIPS Compliance

Certificate Compliance Check

#!/bin/bash
# check-fips-compliance.sh
# Verify certificate is FIPS-compliant

CERT=$1

if [ -z "$CERT" ] || [ ! -f "$CERT" ]; then
  echo "Usage: $0 /path/to/certificate.crt"
  exit 1
fi

echo "=== FIPS Compliance Check ==="
echo "Certificate: $CERT"
echo ""

COMPLIANT=true

# Check signature algorithm
SIG_ALG=$(openssl x509 -in "$CERT" -noout -text | grep "Signature Algorithm" | head -2)
echo "Signature Algorithm: $SIG_ALG"

if echo "$SIG_ALG" | grep -Eqi "md5|sha1"; then
  echo "  ❌ FAIL: MD5/SHA-1 not FIPS-approved"
  COMPLIANT=false
else
  echo "  ✅ PASS: FIPS-approved signature"
fi

# Check key size
KEY_SIZE=$(openssl x509 -in "$CERT" -noout -text | grep "Public-Key" | grep -oP '\d+')
echo ""
echo "Key Size: $KEY_SIZE bits"

if [ "$KEY_SIZE" -lt 2048 ]; then
  echo "  ❌ FAIL: Key size < 2048 bits"
  COMPLIANT=false
else
  echo "  ✅ PASS: Key size adequate"
fi

# Check key algorithm
KEY_ALG=$(openssl x509 -in "$CERT" -noout -text | grep "Public Key Algorithm")
echo ""
echo "Key Algorithm: $KEY_ALG"

if echo "$KEY_ALG" | grep -qi "dsa"; then
  echo "  ❌ FAIL: DSA not FIPS-approved"
  COMPLIANT=false
fi

# Final result
echo ""
echo "================================"
if [ "$COMPLIANT" = true ]; then
  echo "✅ Certificate is FIPS-COMPLIANT"
  exit 0
else
  echo "❌ Certificate is NOT FIPS-compliant"
  echo "   Reissue with FIPS-approved parameters"
  exit 1
fi

39.4 FIPS CA Selection

CA Must Be FIPS-Validated

Internal CA:

  • Use FreeIPA in FIPS mode
  • Dogtag PKI (FreeIPA’s CA) has FIPS validation

External CA:

  • Verify CA is FIPS 140-2/140-3 validated
  • Request FIPS compliance documentation
  • Common FIPS CAs: DigiCert Federal, Entrust, IdenTrust

39.5 Service Configuration for FIPS

Services Automatically FIPS-Compliant

When FIPS mode enabled, all services automatically use FIPS crypto-policy:

# Apache - no special config needed
# Just ensure certificate is FIPS-compliant

# NGINX - automatically uses FIPS policy

# Postfix - FIPS-compliant automatically

# Verify each service
openssl s_client -connect localhost:443
# Check cipher used - should be FIPS-approved

39.6 Key Takeaways

  1. FIPS 140-2 is current validated standard on RHEL
  2. FIPS 140-3 transition is in progress
  3. Enable at installation for best results
  4. RSA 2048+ or ECC P-256/384 only
  5. SHA-256+ signatures required
  6. Services automatically comply with FIPS policy
  7. Test applications before enabling FIPS

Quick Reference Card

┌───────────────────────────────────────────────────────────────┐
│ FIPS-COMPLIANT CERTIFICATES QUICK REFERENCE                   │
├───────────────────────────────────────────────────────────────┤
│ Standard:   FIPS 140-2 (current validated)                    │
│             FIPS 140-3 (transition in progress)               │
│                                                               │
│ Keys:       RSA 2048/3072/4096                                │
│             ECC P-256/384/521                                 │
│                                                               │
│ Signature:  SHA-256, SHA-384, SHA-512                         │
│             (NO MD5, NO SHA-1)                                │
│                                                               │
│ Generate:   openssl genpkey -algorithm RSA ... (in FIPS mode) │
│ CSR:        openssl req -new -sha256 ...                      │
│ Verify:     Check signature alg, key size                     │
│                                                               │
│ Test:       echo test | openssl md5                           │
│             (should fail if FIPS working)                     │
└───────────────────────────────────────────────────────────────┘

✅ FIPS mode must be enabled for compliance
✅ All operations use validated cryptographic modules
⚠️ Check current 140-2/140-3 status for your needs

Chapter Navigation

Chapter 40: RHEL Security Hardening for Certificates

Defense in Depth: Beyond FIPS, learn how to harden certificate security on RHEL using SELinux, TPM, smart cards, and security scanning tools.


40.1 Security Hardening Overview

Layers of Certificate Security:

  1. File Permissions - Protect private keys
  2. SELinux - Mandatory access control
  3. Firewall - Limit exposure
  4. Auditing - Track access
  5. TPM - Hardware key protection
  6. Smart Cards - Physical tokens
  7. Monitoring - Detect issues
  8. Compliance Scanning - Verify configuration

40.2 SELinux for Certificates

Proper SELinux Contexts

#============================================#
# SELINUX CERTIFICATE CONTEXTS
#============================================#

# Check current contexts
ls -Z /etc/pki/tls/certs/*.crt
ls -Z /etc/pki/tls/private/*.key

# Correct contexts:
# Certificates: system_u:object_r:cert_t:s0
# Private keys: system_u:object_r:cert_t:s0

# Fix contexts if wrong
sudo restorecon -Rv /etc/pki/tls/

# Verify
ls -Z /etc/pki/tls/certs/server.crt
# system_u:object_r:cert_t:s0  ← Correct

SELinux Certificate Policy

#============================================#
# SELINUX CERTIFICATE HARDENING
#============================================#

# Ensure SELinux enforcing
getenforce
# Enforcing  ← Good

# If permissive, enable enforcing
sudo setenforce 1

# Make permanent
sudo sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config

# Check for certificate-related denials
sudo ausearch -m avc -ts recent | grep cert

# If denials found, generate policy
sudo ausearch -m avc -ts recent | audit2allow -M mycert-policy
sudo semodule -i mycert-policy.pp

40.3 File Permissions Hardening

Strict Permission Model

#============================================#
# HARDENED FILE PERMISSIONS
#============================================#

# Certificates (public) - minimal access
sudo chmod 444 /etc/pki/tls/certs/*.crt
sudo chown root:root /etc/pki/tls/certs/*.crt

# Private keys (secret!) - owner only
sudo chmod 400 /etc/pki/tls/private/*.key
sudo chown root:root /etc/pki/tls/private/*.key

# Even stricter: Immutable (can't be modified even by root without removing flag)
sudo chattr +i /etc/pki/tls/certs/critical.crt
sudo chattr +i /etc/pki/tls/private/critical.key

# Remove immutable when need to update
# sudo chattr -i /etc/pki/tls/private/critical.key

# Verify
ls -l /etc/pki/tls/private/
# -r--------. 1 root root  ← 400, very restrictive

40.4 TPM (Trusted Platform Module)

Using TPM for Key Storage

TPM Benefits:

  • ✅ Hardware-protected keys
  • ✅ Keys never leave TPM
  • ✅ Tamper-resistant
  • ✅ Platform attestation
#============================================#
# TPM FOR CERTIFICATE KEYS (ADVANCED)
#============================================#

# Check if TPM available
ls /dev/tpm*

# Install TPM tools
sudo dnf install tpm2-tools -y

# Generate key in TPM
tpm2_createprimary -C o -g sha256 -G rsa -c primary.ctx
tpm2_create -G rsa -u rsa.pub -r rsa.priv -C primary.ctx

# Use TPM key with OpenSSL requires additional setup
# (Complex, enterprise use case)

# For certmonger with TPM:
# Experimental/advanced - check Red Hat docs

40.5 Smart Cards and PIV

Using Smart Cards for Authentication

#============================================#
# SMART CARD SETUP (PIV/CAC)
#============================================#

# Install smart card support
sudo dnf install opensc pcsc-lite -y

# Start PC/SC daemon
sudo systemctl enable --now pcscd

# Check if card readable
pkcs11-tool --list-slots

# List certificates on card
pkcs11-tool --list-objects

# Use smart card with SSH
# /etc/ssh/sshd_config:
# PubkeyAuthentication yes

# Extract public key from card
ssh-keygen -D /usr/lib64/opensc-pkcs11.so > ~/.ssh/authorized_keys

40.6 Audit and Monitoring

auditd for Certificate Access

#============================================#
# AUDIT CERTIFICATE ACCESS
#============================================#

# Add audit rules for private key access
sudo auditctl -w /etc/pki/tls/private/ -p war -k certificate-access

# Make permanent
echo "-w /etc/pki/tls/private/ -p war -k certificate-access" | \
  sudo tee -a /etc/audit/rules.d/certificate.rules

# Reload rules
sudo augenrules --load

# Monitor access
sudo ausearch -k certificate-access

# Real-time monitoring
sudo ausearch -k certificate-access -ts recent -i

40.7 OpenSCAP Scanning

Security Compliance Scanning

#============================================#
# OPENSCAP CERTIFICATE SCANNING
#============================================#

# Install OpenSCAP
sudo dnf install openscap-scanner scap-security-guide -y

# Scan for certificate issues
sudo oscap xccdf eval \
  --profile xccdf_org.ssgproject.content_profile_pci-dss \
  --results scan-results.xml \
  --report scan-report.html \
  /usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml

# View report
firefox scan-report.html

# Certificate-related checks:
# - File permissions
# - SELinux contexts
# - Weak algorithms
# - Expiration

40.8 Security Hardening Checklist

## Certificate Security Hardening Checklist

### File Security
- [ ] Private keys mode 400 or 600 (never 644!)
- [ ] Certificates mode 444 or 644
- [ ] Ownership: root:root or service user
- [ ] SELinux contexts: cert_t
- [ ] Consider immutable flag (+i) for critical certs

### Access Control
- [ ] SELinux enforcing
- [ ] Audit rules for private key access
- [ ] Firewall limiting TLS ports
- [ ] Principle of least privilege applied

### Algorithm Security
- [ ] SHA-256+ signatures only
- [ ] RSA 2048+ or ECC P-256+ keys
- [ ] TLS 1.2+ only (no 1.0/1.1)
- [ ] Strong ciphers (via crypto-policy)
- [ ] FIPS mode if required

### Operational Security
- [ ] Certificates monitored for expiration
- [ ] Automatic renewal enabled (certmonger)
- [ ] Backups encrypted
- [ ] Keys never emailed or in tickets
- [ ] Access logged and reviewed
- [ ] Regular security scans

### Network Security
- [ ] Firewall rules restrictive
- [ ] Only necessary ports open
- [ ] Certificate pinning (where applicable)
- [ ] HSTS enabled for web servers
- [ ] OCSP stapling enabled

### Compliance
- [ ] OpenSCAP scans passing
- [ ] STIG compliance verified
- [ ] CIS benchmarks met
- [ ] Documentation current
- [ ] Audit trail maintained

40.9 Key Takeaways

  1. Defense in depth - Multiple security layers
  2. SELinux enforcing - Mandatory for production
  3. File permissions critical - 400/600 for keys
  4. Audit everything - Track key access
  5. TPM for high security - Hardware protection
  6. OpenSCAP for compliance - Automated scanning
  7. Monitor continuously - Security is ongoing

Quick Reference Card

┌──────────────────────────────────────────────────────────────┐
│ CERTIFICATE SECURITY HARDENING                               │
├──────────────────────────────────────────────────────────────┤
│ Permissions:  chmod 400 /etc/pki/tls/private/*.key           │
│               chmod 444 /etc/pki/tls/certs/*.crt             │
│                                                              │
│ SELinux:      getenforce (must be Enforcing)                 │
│               restorecon -Rv /etc/pki/tls/                   │
│               ls -Z (check contexts)                         │
│                                                              │
│ Audit:        auditctl -w /etc/pki/tls/private/ -p war       │
│               ausearch -k certificate-access                 │
│                                                              │
│ Scan:         oscap xccdf eval --profile pci-dss ...         │
│                                                              │
│ Immutable:    chattr +i /etc/pki/tls/private/key.key         │
│               chattr -i (to modify)                          │
└──────────────────────────────────────────────────────────────┘

✅ SELinux enforcing is mandatory
✅ Audit private key access
✅ Use 400 (not 600) for maximum security

🧪 Hands-On Lab

Lab 20: Security Hardening

Apply security best practices to certificate configurations

  • 📁 Location: labs/en_US/20-security-hardening/
  • ⏱️ Time: 30-40 minutes
  • 🎯 Level: Advanced

Chapter Navigation

Chapter 41: Compliance & Auditing

Meet Requirements: Learn how to meet security compliance requirements (STIG, CIS, PCI-DSS) and audit certificate configurations on RHEL.


41.1 Compliance Frameworks

FrameworkFocusCertificate Requirements
STIGDoD SecurityFIPS, strong algorithms, auditing
CIS BenchmarkIndustry best practicesTLS 1.2+, strong ciphers, permissions
PCI-DSSPayment card industryStrong crypto, no weak TLS/ciphers
HIPAAHealthcareEncryption, access control, auditing
NIST 800-53Federal systemsFIPS, approved algorithms, monitoring

41.2 STIG Compliance

DISA STIG Requirements for Certificates

Key STIG Requirements:

## Certificate STIG Controls

### V-238200: SSH must use strong ciphers
- Requirement: Only FIPS-approved algorithms
- Check: /etc/ssh/sshd_config
- Fix: Use crypto-policies (RHEL 8+)

### V-238201: Web server must use strong TLS
- Requirement: TLS 1.2+ only
- Check: Apache/NGINX configuration
- Fix: Disable TLS 1.0/1.1

### V-238202: Certificates must be from DoD-approved CA
- Requirement: Use approved CA
- Check: Certificate issuer
- Fix: Obtain from approved source

### V-238203: Private keys must be protected
- Requirement: Mode 600 or stricter
- Check: ls -l /etc/pki/tls/private/
- Fix: chmod 600

### V-238204: Certificate expiration must be monitored
- Requirement: Automated monitoring
- Check: Monitoring system in place
- Fix: Implement (see Chapter 26)

STIG Compliance Scanning

#============================================#
# STIG COMPLIANCE SCAN FOR CERTIFICATES
#============================================#

# Install SCAP Security Guide
sudo dnf install scap-security-guide openscap-scanner -y

# Run STIG scan
sudo oscap xccdf eval \
  --profile xccdf_org.ssgproject.content_profile_stig \
  --results stig-results.xml \
  --report stig-report.html \
  /usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml

# View report
firefox stig-report.html

# Check certificate-specific findings
grep -i "cert\|tls\|ssl" stig-report.html

41.3 CIS Benchmark Compliance

CIS Controls for Certificates

CIS RHEL Benchmark Recommendations:

## CIS Certificate Controls

### 5.2.14: Ensure only strong ciphers are used
- Check: Crypto-policy DEFAULT or FUTURE
- Command: `update-crypto-policies --show`

### 5.2.15: Ensure only strong algorithms used
- Check: No MD5, SHA-1, weak keys
- Scan: Check all certificates

### 5.2.16: Ensure TLS 1.2 minimum
- Check: Crypto-policy or service config
- Test: `openssl s_client -tls1_2`

### 5.3.1: Ensure permissions on private keys
- Requirement: 600 or stricter
- Check: `ls -l /etc/pki/tls/private/`

### 5.3.2: Ensure certificate expiration monitoring
- Requirement: Automated checks
- Implementation: certmonger or monitoring script

CIS Compliance Scan

#============================================#
# CIS BENCHMARK SCAN
#============================================#

# Run CIS scan
sudo oscap xccdf eval \
  --profile xccdf_org.ssgproject.content_profile_cis \
  --results cis-results.xml \
  --report cis-report.html \
  /usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml

# Generate remediation script
sudo oscap xccdf generate fix \
  --profile xccdf_org.ssgproject.content_profile_cis \
  --fix-type bash \
  stig-results.xml > remediation.sh

# Review and run remediation
chmod +x remediation.sh
sudo ./remediation.sh

41.4 PCI-DSS Compliance

PCI-DSS Certificate Requirements

PCI-DSS v4.0 Requirements:

## PCI-DSS Certificate Controls

### Requirement 4.2.1: Strong cryptography
- TLS 1.2 minimum (1.3 recommended)
- Strong cipher suites only
- Check: crypto-policy DEFAULT or FUTURE

### Requirement 4.2.1.1: Insecure protocols disabled
- NO SSL, TLS 1.0, TLS 1.1
- Check: `openssl s_client -tls1`
- Should fail on compliant system

### Requirement 4.2.1.2: Strong encryption algorithms
- AES-128 minimum
- NO 3DES, DES, RC4
- Check: `openssl ciphers -v`

### Requirement 8.3.2: Certificate-based authentication
- For administrative access
- Implementation: Client certificates, smart cards

### Requirement 10: Audit certificate access
- Log all private key access
- Implementation: auditd rules

PCI-DSS Validation Script

#!/bin/bash
# pci-dss-cert-check.sh

echo "=== PCI-DSS Certificate Compliance Check ==="

# Check 1: TLS 1.2+ only
echo "1. TLS Version Check:"
if openssl s_client -connect localhost:443 -tls1 &>/dev/null; then
  echo "  ❌ FAIL: TLS 1.0 is enabled"
else
  echo "  ✅ PASS: TLS 1.0 disabled"
fi

# Check 2: Strong ciphers
echo ""
echo "2. Cipher Strength:"
WEAK=$(openssl ciphers -v | grep -Ei "3des|rc4|des-cbc" | wc -l)
if [ $WEAK -gt 0 ]; then
  echo "  ❌ FAIL: Weak ciphers available"
else
  echo "  ✅ PASS: No weak ciphers"
fi

# Check 3: Certificate expiration monitoring
echo ""
echo "3. Expiration Monitoring:"
if systemctl is-active --quiet certmonger || \
   systemctl list-timers | grep -q cert-monitor; then
  echo "  ✅ PASS: Monitoring enabled"
else
  echo "  ⚠️ WARNING: No automated monitoring detected"
fi

# Check 4: Private key permissions
echo ""
echo "4. Private Key Permissions:"
BAD_PERMS=$(find /etc/pki/tls/private/ -name "*.key" -not -perm 600 2>/dev/null | wc -l)
if [ $BAD_PERMS -gt 0 ]; then
  echo "  ❌ FAIL: $BAD_PERMS keys with wrong permissions"
else
  echo "  ✅ PASS: All keys properly protected"
fi

echo ""
echo "=== Check Complete ==="

41.5 Audit Procedures

Certificate Audit Checklist

## Quarterly Certificate Audit Checklist

### Inventory Review
- [ ] All certificates documented
- [ ] Certificate inventory current
- [ ] Ownership documented
- [ ] Purpose documented

### Expiration Review
- [ ] No expired certificates
- [ ] No certificates expiring < 30 days
- [ ] Renewal process documented
- [ ] Monitoring alerts working

### Security Review
- [ ] SHA-256+ signatures only (no SHA-1 or MD5)
- [ ] RSA 2048+ or ECC P-256+ keys
- [ ] TLS 1.2+ only (no 1.0/1.1)
- [ ] Private key permissions correct (600)
- [ ] SELinux contexts correct
- [ ] No unnecessary certificates

### Configuration Review
- [ ] Service configurations reviewed
- [ ] Crypto-policy appropriate
- [ ] No weak cipher overrides
- [ ] HSTS enabled (web servers)
- [ ] Certificate pinning documented

### Access Review
- [ ] Audit logs reviewed
- [ ] Unauthorized access investigated
- [ ] Key access limited to authorized personnel
- [ ] Backup access controlled

### Compliance Review
- [ ] STIG/CIS/PCI compliance verified
- [ ] Security scans passing
- [ ] Remediation complete
- [ ] Documentation updated

41.6 Automated Compliance Reporting

Generate Compliance Report

#!/bin/bash
# generate-compliance-report.sh

REPORT_FILE="compliance-report-$(date +%Y%m%d).txt"

cat > "$REPORT_FILE" << EOF
=== Certificate Compliance Report ===
Generated: $(date)
System: $(hostname)
RHEL Version: $(cat /etc/redhat-release)

=== Configuration ===
OpenSSL Version: $(openssl version)
Crypto-Policy: $(update-crypto-policies --show 2>/dev/null || echo "N/A (RHEL 7)")
FIPS Mode: $(fips-mode-setup --check 2>/dev/null || echo "N/A")
SELinux: $(getenforce)

=== Certificate Inventory ===
EOF

# Count certificates
TOTAL=$(find /etc/pki/tls/certs/ -name "*.crt" -type f 2>/dev/null | wc -l)
echo "Total Certificates: $TOTAL" >> "$REPORT_FILE"

# Check expirations
echo "" >> "$REPORT_FILE"
echo "Expiration Status:" >> "$REPORT_FILE"
EXPIRING=0
for cert in /etc/pki/tls/certs/*.crt; do
  [ -f "$cert" ] || continue
  if ! openssl x509 -in "$cert" -noout -checkend $((86400*30)) 2>/dev/null; then
    echo "  ⚠️ Expires within 30 days: $cert" >> "$REPORT_FILE"
    ((EXPIRING++))
  fi
done
echo "Certificates expiring < 30 days: $EXPIRING" >> "$REPORT_FILE"

# Check algorithms
echo "" >> "$REPORT_FILE"
echo "Algorithm Compliance:" >> "$REPORT_FILE"
SHA1_COUNT=0
for cert in /etc/pki/tls/certs/*.crt; do
  [ -f "$cert" ] || continue
  if openssl x509 -in "$cert" -noout -text 2>/dev/null | grep -qi "sha1.*signature"; then
    echo "  ❌ SHA-1 signature: $cert" >> "$REPORT_FILE"
    ((SHA1_COUNT++))
  fi
done
echo "SHA-1 certificates: $SHA1_COUNT (should be 0)" >> "$REPORT_FILE"

# Check permissions
echo "" >> "$REPORT_FILE"
echo "Permission Compliance:" >> "$REPORT_FILE"
BAD_PERMS=$(find /etc/pki/tls/private/ -name "*.key" -not -perm 600 2>/dev/null | wc -l)
echo "Keys with incorrect permissions: $BAD_PERMS (should be 0)" >> "$REPORT_FILE"

# certmonger status
if command -v getcert &>/dev/null; then
  echo "" >> "$REPORT_FILE"
  echo "certmonger Status:" >> "$REPORT_FILE"
  sudo getcert list | grep "status:" | sort | uniq -c >> "$REPORT_FILE"
fi

echo "" >> "$REPORT_FILE"
echo "=== Report Complete ===" >> "$REPORT_FILE"

cat "$REPORT_FILE"
echo ""
echo "Report saved to: $REPORT_FILE"

41.7 Key Takeaways

  1. Compliance is ongoing - Not one-time
  2. Multiple frameworks exist - STIG, CIS, PCI, HIPAA
  3. OpenSCAP automates scanning on RHEL
  4. Document everything - Required for audits
  5. Regular audits essential - Quarterly minimum
  6. Remediation must be tracked - Fix and verify
  7. Monitoring is compliance - Continuous validation

Quick Reference Card

┌──────────────────────────────────────────────────────────────┐
│ COMPLIANCE & AUDITING QUICK REFERENCE                        │
├──────────────────────────────────────────────────────────────┤
│ STIG:         oscap ... --profile stig                       │
│ CIS:          oscap ... --profile cis                        │
│ PCI-DSS:      oscap ... --profile pci-dss                    │
│                                                              │
│ Common Requirements:                                         │
│   - TLS 1.2+ only                                            │
│   - Strong algorithms (SHA-256+, RSA 2048+)                  │
│   - No weak ciphers (3DES, RC4)                              │
│   - Private keys protected (mode 600)                        │
│   - Expiration monitoring                                    │
│   - Audit logging enabled                                    │
│   - FIPS mode (for federal)                                  │
│                                                              │
│ Tools:        OpenSCAP, aide, auditd                         │
│ Scan:         oscap xccdf eval --profile <profile> ...       │
│ Remediate:    oscap xccdf generate fix ...                   │
└──────────────────────────────────────────────────────────────┘

✅ Compliance is continuous, not one-time
✅ Automate scanning with OpenSCAP
✅ Document all configurations and exceptions

Chapter Navigation

Learning Path Guide

How to use this tutorial effectively based on your role and experience level.


🎯 For Complete Beginners

Goal: Learn certificates from zero

Path: Read in order (8 weeks)

Week 1: Foundations (Ch 1-7)

  • Ch 1: Cryptography, PKI Structure & Fundamentals
  • Ch 2: Introduction to Certificates on RHEL
  • Ch 3: RHEL Certificate Tools Overview
  • Ch 4: Basic Cryptography for RHEL Admins
  • Ch 5: X.509 Certificates on RHEL
  • Ch 6: RHEL Trust Store Deep Dive
  • Ch 7: Digital Signatures & Verification
  • Outcome: Understand certificates, trust chains, and RHEL tools

Week 2: Version Mastery (Ch 8-13)

  • Ch 8: Differences between versions
  • Ch 9: RHEL 7
  • Ch 10: RHEL 8
  • Ch 11: RHEL 9
  • Ch 12: RHEL 10
  • Ch 13: Compatibility
  • Outcome: Know version differences

Week 3-4: Services (Ch 14-21)

  • Configure Apache, NGINX, Postfix, LDAP, Databases, FreeIPA
  • Outcome: Can configure any service

Week 5: Automation (Ch 22-26)

  • certmonger, crypto-policies, Ansible, monitoring
  • Outcome: Automate certificate lifecycle

Week 6: Troubleshooting (Ch 27-33)

  • Master systematic troubleshooting
  • Outcome: Can fix any certificate issue! ⭐

Week 7: Migration (Ch 34-37)

  • RHEL upgrade procedures
  • Outcome: Safely migrate RHEL versions

Week 8: Security (Ch 38-41)

  • FIPS, hardening, compliance
  • Outcome: Meet security requirements

🔧 For System Administrators

Goal: Configure and maintain certificates

Recommended Path:

  1. Quick Start (3-5 hours)

    • Ch 1: Cryptography & PKI Fundamentals
    • Ch 3: Tools
    • Ch 27: Troubleshooting Methodology
  2. Your RHEL Version (1-2 hours)

    • Ch 9 (RHEL 7), Ch 10 (RHEL 8), Ch 11 (RHEL 9), or Ch 12 (RHEL 10)
  3. Your Services (3-4 hours)

    • Ch 14-21: Pick chapters for services you use
  4. Automation (2-3 hours)

    • Ch 22: certmonger
    • Ch 23: Crypto-policies (if RHEL 8+)
  5. Reference

    • Keep Ch 27-33 handy for troubleshooting

Total Time: ~10-15 hours to proficiency


🚨 For Support Engineers

Goal: Troubleshoot certificate issues fast

Fast Track Path:

  1. Start Here (1 hour)

    • Ch 27: Troubleshooting Methodology ⭐
    • TROUBLESHOOTING-QUICK-START.md
  2. Common Issues (2 hours)

    • Ch 28: Common Errors
    • Ch 29: Service-Specific
    • Ch 30: certmonger Issues
    • Ch 31: Crypto-Policy Issues
  3. Tools (1 hour)

    • Ch 3: RHEL Tools
    • Ch 32: SOS Reports
  4. Emergency (30 min)

    • Ch 33: Emergency Procedures
  5. Reference as Needed

    • Ch 9-12: Version-specific chapters
    • Ch 14-21: Service chapters

Total Time: 5-8 hours to troubleshooting proficiency

Then: Use chapters as reference during incidents


🏢 For Enterprise Architects

Goal: Design certificate infrastructure

Strategic Path:

  1. Overview (1 hour)

    • Ch 1-2: Fundamentals and introduction
  2. Enterprise CA (2 hours)

    • Ch 19: FreeIPA
  3. Automation (3 hours)

    • Ch 22: certmonger
    • Ch 23: Crypto-policies
    • Ch 25: Ansible
  4. Best Practices (2 hours)

    • Ch 21: Service Best Practices
    • Ch 26: Monitoring
  5. Security (3 hours)

    • Ch 38-41: FIPS, hardening, compliance
  6. Migration (2 hours)

    • Ch 34-37: If planning upgrades

Total Time: ~13 hours for architecture knowledge


🔒 For Security/Compliance Teams

Goal: Ensure compliance and security

Compliance Path:

  1. Foundation (1 hour)

    • Ch 1: Cryptography & PKI Fundamentals
    • Ch 2: Introduction to Certificates on RHEL
  2. Security Focus (4 hours)

    • Ch 38: FIPS Mode
    • Ch 39: FIPS Certificates
    • Ch 40: Security Hardening
    • Ch 41: Compliance & Auditing ⭐
  3. Crypto-Policies (1 hour)

    • Ch 23: Understanding system-wide controls
  4. Monitoring (1 hour)

    • Ch 26: Monitoring & Alerting
  5. Audit Procedures (1 hour)

    • Ch 32: SOS Reports
    • Ch 41: Auditing procedures

Total Time: ~8-10 hours for compliance expertise


🎓 For Training/Certification Prep

Goal: Complete mastery

Complete Path: All chapters in order

Time Investment: 40-50 hours

Outcome: Expert-level knowledge of RHEL certificate management


📚 Jump-In Points

By Problem Type:

“Service won’t start” → Ch 28 (Common Errors), Ch 29 (Service-Specific)

“Clients can’t connect” → Ch 13 (Compatibility), Ch 31 (Crypto-Policy)

“certmonger not renewing” → Ch 30 (certmonger Troubleshooting)

“Planning RHEL upgrade” → Ch 34-37 (Migration)

“Need FIPS compliance” → Ch 38-39 (FIPS)

“Setting up new service” → Ch 14-21 (Service chapters)

By RHEL Version:

Using RHEL 7 → Ch 9 (RHEL 7 Management)

Using RHEL 8 → Ch 10 (Crypto-Policies are key!)

Using RHEL 9 → Ch 11 (OpenSSL 3.x, SHA-1 blocked)

Using RHEL 10 → Ch 12 (Latest features)

Mixed environment → Ch 13 (Cross-Version Compatibility)


🗺️ Tutorial Map

START HERE
    │
    ├─ New to certificates?
    │   └─ Ch 1 → Ch 2 → Ch 3 → Continue in order
    │
    ├─ Need to troubleshoot NOW?
    │   └─ Ch 27 → Ch 28 → Ch 29 → Ch 33
    │
    ├─ Configuring a service?
    │   └─ Ch 14-21 (pick your service)
    │
    ├─ Planning migration?
    │   └─ Ch 34 → Ch 35/36 → Ch 37
    │
    ├─ Need automation?
    │   └─ Ch 22 (certmonger) → Ch 23 (crypto-policies)
    │
    └─ Compliance required?
        └─ Ch 38-41 (FIPS, security, auditing)

⏱️ Time Estimates

PathTimeChapters
Quick Start3-5 hours1, 3, 27
Troubleshooting5-8 hours27-33
Complete Beginner40-50 hoursAll in order
Sysadmin10-15 hoursSelected chapters
Support Engineer5-8 hoursTroubleshooting focus
Compliance8-10 hoursSecurity chapters

💡 Study Tips

  1. Hands-on is essential - Practice on RHEL VM
  2. Follow examples - Copy-paste and understand
  3. Use quick references - At end of each chapter
  4. Bookmark troubleshooting - Chapters 27-33
  5. Know your RHEL version - Focus on relevant chapters
  6. Build a lab - Use FreeIPA for practice

Start Learning: Chapter 1: Cryptography, PKI Structure & Fundamentals →

Need Help Fast: Troubleshooting Quick-Start →

Version Reference: RHEL Version Cheat Sheet →

Troubleshooting Quick-Start Guide

When you have a certificate problem, start here!


🚨 Emergency? Jump to Chapter 33!

If production is down, go immediately to Chapter 33: Emergency Procedures


📋 The 7-Step Method (Chapter 27)

1. Identify: RHEL version, OpenSSL, crypto-policy
2. Verify: Expiry, hostname, key match, algorithm
3. Trust: CA validation, chain, intermediates
4. Config: Service files, paths, permissions
5. System: Crypto-policy, FIPS, SELinux, firewall
6. Test: Live connections, curl, openssl s_client
7. Logs: Service logs, journal, SELinux audit

Full methodology: Chapter 27


⚡ Quick Diagnostics

First 60 Seconds

# What RHEL version?
cat /etc/redhat-release

# Certificate expired?
openssl x509 -in /etc/pki/tls/certs/server.crt -noout -checkend 0

# Service running?
systemctl status httpd

# Recent errors?
journalctl -xe | grep -i cert | tail -20

# Crypto-policy? (RHEL 8+)
update-crypto-policies --show

🔍 Common Problems

Certificate Expired

# Check
openssl x509 -in cert.crt -noout -dates

# Fix
sudo getcert resubmit -f cert.crt  # If using certmonger tracking
# Or renew manually, or use Chapter 33 emergency procedures

Trust Chain Broken

# Check
openssl verify cert.crt

# Fix
sudo cp ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

Permission Denied

# Check
ls -l /etc/pki/tls/private/server.key

# Fix
sudo chmod 600 /etc/pki/tls/private/server.key
sudo chown root:root /etc/pki/tls/private/server.key

Hostname Mismatch

# Check
openssl x509 -in cert.crt -noout -ext subjectAltName

# Fix
# Reissue certificate with correct SANs

No Shared Cipher (RHEL 8+)

# Check
update-crypto-policies --show

# Temporary fix
sudo update-crypto-policies --set LEGACY
sudo systemctl restart httpd

# Proper fix: Update client to support TLS 1.2+

SHA-1 Rejected (RHEL 9+)

# Check
openssl x509 -in cert.crt -noout -text | grep "Signature Algorithm"

# Fix
# Must reissue with SHA-256+ (no workaround)

📖 Where to Look

Problem TypeGo To Chapter
General troubleshootingChapter 27
Common errorsChapter 28
Apache/NGINX/Postfix issuesChapter 29
certmonger problemsChapter 30
crypto-policy issuesChapter 31
SOS report analysisChapter 32
Production emergencyChapter 33
RHEL 7 specificChapter 9
RHEL 8 specificChapter 10
RHEL 9 specificChapter 11
RHEL 10 specificChapter 12
After migrationChapters 35-36

⚙️ Service-Specific Commands

# Apache
apachectl configtest
tail -f /var/log/httpd/ssl_error_log

# NGINX
nginx -t
tail -f /var/log/nginx/error.log

# Postfix
postfix check
tail -f /var/log/maillog | grep TLS

# OpenLDAP
slapcat -b "cn=config" | grep TLS
# Note: Keys must be owned by ldap:ldap!

# PostgreSQL
sudo -u postgres psql -c "SHOW ssl;"
# Note: Keys must be owned by postgres:postgres!

# certmonger
getcert list
journalctl -u certmonger -f

🎯 Quick Reference

Most Common Issues:

  1. Certificate expired → Renew
  2. Missing CA → Add to trust store
  3. Wrong permissions → chmod 600
  4. Cert/key mismatch → Regenerate CSR
  5. Hostname mismatch → Reissue with SANs
  6. TLS version → Check crypto-policy
  7. SELinux denying → restorecon
  8. certmonger CA_UNREACHABLE → Check IPA/Kerberos

Emergency: Chapter 33

Methodology: Chapter 27

RHEL Version Cheat Sheet for Certificates

Quick reference for certificate differences across RHEL versions.


Version Overview

RHELReleasedOpenSSLTLS SupportCrypto-PoliciesKey Feature
720141.0.2k-261.0/1.1/1.2❌ NoManual config
820191.1.1k-141.2/1.3NEW!System-wide policies
9May 20223.5.5-21.2/1.3✅ EnhancedOpenSSL 3.x, strict
10May 20253.5.5-21.3 pref✅ EnhancedPQC prep, modern

Quick Detection

# Check RHEL version
cat /etc/redhat-release

# Check OpenSSL (indirect version check)
openssl version
# 1.0.2k = RHEL 7
# 1.1.1k = RHEL 8
# 3.5.5  = RHEL 9 or 10

# Check crypto-policies (RHEL 8+ only)
update-crypto-policies --show 2>/dev/null || echo "RHEL 7 (no crypto-policies)"

TLS Configuration by Version

RHEL 7

# Manual configuration required everywhere
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite HIGH:!aNULL:!MD5:!3DES

RHEL 8/9/10

# crypto-policies handle it automatically!
# No SSLProtocol or SSLCipherSuite needed
# Just include certificate paths

Common Commands by Version

TaskRHEL 7RHEL 8/9/10
Generate Keyopenssl genrsa -out key 2048openssl genpkey -algorithm RSA -out key
Check PolicyN/Aupdate-crypto-policies --show
TLS ConfigManual per serviceAutomatic via crypto-policies
certmongerBasicEnhanced (IPA/tracking workflows)

Troubleshooting by Version

RHEL 7

  • Check for TLS 1.0/1.1 issues
  • Manual cipher configurations
  • No crypto-policies

RHEL 8

  • Check crypto-policy first!
  • TLS 1.0/1.1 disabled in DEFAULT
  • LEGACY policy for compatibility

RHEL 9

  • OpenSSL 3.x provider issues
  • SHA-1 BLOCKED
  • Use -provider legacy for old algorithms

RHEL 10

  • Same as RHEL 9
  • Even stricter defaults
  • Check minor version docs

Migration Impact

MigrationCertificate ImpactKey Changes
7→8Moderate-Highcrypto-policies, TLS 1.0/1.1 blocked
8→9HighOpenSSL 3.x, SHA-1 blocked, stricter
9→10LowSame OpenSSL, incremental hardening

Quick Fixes by Version

“no shared cipher” Error

  • RHEL 7: Update cipher config manually
  • RHEL 8/9/10: sudo update-crypto-policies --set LEGACY (temp!)

SHA-1 Certificate

  • RHEL 7/8: Works (deprecated)
  • RHEL 9/10: BLOCKED - must reissue

TLS 1.0 Client

  • RHEL 7: Works by default
  • RHEL 8/9/10: Blocked in DEFAULT, use LEGACY (temp!)

Full Details: See Chapters 9-12

Appendix A: Kubernetes cert-manager

cert-manager is a CNCF project that automates certificate issuance inside Kubernetes clusters.

1. Architecture

  • Issuer / ClusterIssuer – Defines CA or ACME server.
  • Certificate – Desired state for a cert (DNS names, duration).
  • Controller – Reconciles resources, stores secrets.
graph LR
  subgraph K8s
    A[Certificate] --> B[Secret TLS]
    A --> C(Issuer)
  end
  C -->|ACME| LE[Let's Encrypt]

2. Installation

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.1/cert-manager.yaml

3. Example: Ingress TLS

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: le-key
    solvers:
    - http01:
        ingress:
          class: nginx
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: web-tls
  namespace: default
spec:
  secretName: web-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  commonName: example.com
  dnsNames:
  - example.com
  - www.example.com

4. Renewal & Status

kubectl describe certificate web-tls shows condition Ready and next renewal time.


🧪 Hands-On Lab

Lab 21: Kubernetes cert-manager

Automate certificate management in Kubernetes

  • 📁 Location: labs/en_US/21-kubernetes-cert-manager/
  • ⏱️ Time: 50-60 minutes
  • 🎯 Level: Advanced

Appendix B: HashiCorp Vault PKI

Vault provides a dynamic PKI where certificates are issued on-demand with short TTLs.

1. Why Vault?

  • Centralised policy enforcement
  • Dynamic, short-lived certs reduce revocation needs
  • API-driven (REST + CLI)

2. Enabling PKI Engine

vault secrets enable pki
vault secrets tune -max-lease-ttl=87600h pki
vault write pki/root/generate/internal common_name="corp.example" ttl=87600h
vault write pki/config/urls issuing_certificates="https://vault.corp/v1/pki/ca" \
                                    crl_distribution_points="https://vault.corp/v1/pki/crl"

3. Roles & Issue

vault write pki/roles/web allowed_domains="web.corp" allow_subdomains=true max_ttl=72h
vault write pki/issue/web common_name=app01.web.corp ttl=24h

4. Agent Sidecar Auto-Renew

# vault-agent.hcl
pid_file = "/var/run/agent.pid"
auto_auth {
  method "kubernetes" {
    mount_path = "auth/k8s"
    role       = "web"
  }
  sink "file" {
    config = {
      path = "/etc/tls/web.pem"
    }
  }
}

🧪 Hands-On Lab

Lab 22: HashiCorp Vault PKI

Dynamic certificate issuance with Vault

  • 📁 Location: labs/en_US/22-vault-pki/
  • ⏱️ Time: 45-55 minutes
  • 🎯 Level: Advanced

Appendix C: Zero Trust Architecture

PKI in Zero Trust Architecture

Zero Trust (ZT) assumes no implicit trust based on network location. Every request must be authenticated and authorised.

1. Google BeyondCorp & NIST SP 800-207

These frameworks recommend strong identity, transport encryption, and continuous evaluation.

2. PKI’s Role

  • Device identity via certificates.
  • mTLS for east-west traffic.
  • Short-lived credentials auto-rotated.

3. Certificate Profile for ZT

ExtensionPurpose
SAN: URI:spiffe://Workload ID
Key Usage: digitalSignatureAuthN
EKU: clientAuth, serverAuthMutual TLS
Validity ≤ 24hLimit blast radius

4. Policy Enforcement Points

  1. Gateways terminate TLS and verify client certs.
  2. Service Mesh sidecars perform mTLS transparently.
  3. Endpoint Agents maintain device certificates.

5. Lab: Issue SPIFFE Certificates with cert-manager

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: spiffe-workload
spec:
  duration: 24h
  commonName: spiffe://prod/web/api
  uriSANs:
  - spiffe://prod/web/api
  issuerRef:
    name: mesh-issuer
    kind: ClusterIssuer

Appendix D: DevSecOps Integration

PKI in DevSecOps & CI/CD

Integrating certificate management into CI/CD pipelines ensures every build artifact and environment uses trusted identities.

1. Signing Build Artifacts

  • Containers – cosign, Notary v2.
  • Packages – RPM/DEB GPG signatures.
  • Binaries – Windows Authenticode.

2. Automated TLS for Preview Environments

Pipelines trigger cert-manager to issue ephemeral certs for short-lived branches.

3. Example: GitHub Actions with cosign

name: Build & Sign
on: push
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Build image
      run: docker build -t ghcr.io/org/app:${{ github.sha }} .
    - name: Login registry
      run: echo ${{ secrets.GH_TOKEN }} | docker login ghcr.io -u user --password-stdin
    - name: Push image
      run: docker push ghcr.io/org/app:${{ github.sha }}
    - name: Sign image
      env:
        COSIGN_EXPERIMENTAL: "1"
        COSIGN_KEY: ${{ secrets.COSIGN_KEY }}
      run: cosign sign -key env://COSIGN_KEY ghcr.io/org/app:${{ github.sha }}

4. Policy Gates

Supply-chain security scanners verify signatures and enforce policy before deploy.

5. Secrets Management

Store private keys for signing in HashiCorp Vault or AWS KMS, not in repo secrets.

Appendix E: PKI Policy Theory

Policies, Baselines & Audits

1. Why Policies Matter

Policies formalise how certificates may be issued and used, ensuring consistency and legal defensibility.

2. Baseline Requirements (CAB Forum)

Requirements that publicly-trusted CAs must follow, including:

  • Domain validation methods
  • Key sizes ≥ 2048-bit RSA / 256-bit ECC
  • Maximum validity 398 days

3. Certificate Policy (CP) vs CPS

DocumentAudienceContent
CPRelying partiesWhat assurance the PKI provides
CPSAuditors, operatorsHow the CA meets the CP

4. Audits & Compliance

  • WebTrust / ETSI audits for public CAs.
  • Internal PKIs may align with NIST SP 800-53 or ISO 27001.

5. RHEL Case Study

RHEL’s certmonger can automatically renew host certificates in accordance with enterprise CP/CPS guidelines.

Appendix F: IoT Certificates

IoT Device Certificates

Internet of Things devices require unique identities for secure communication. Certificates provide cryptographic proof of device authenticity.

1. Why Certificates for IoT?

  • Device Identity – Each sensor, gateway, or actuator gets a unique cert.
  • Zero-Trust Networks – Devices authenticate mutually with cloud/edge.
  • Supply Chain Security – Certificates provisioned at manufacturing prevent cloning.
  • OTA Updates – Code-signing ensures firmware integrity.

2. Certificate Provisioning Models

Factory Provisioning

Certificates burned into device secure storage (TPM, secure element) before shipment.

sequenceDiagram
    Factory->>HSM: Generate key pair
    HSM->>Factory: Return CSR
    Factory->>CA: Sign CSR
    CA-->>Factory: Certificate
    Factory->>Device: Inject cert + private key

Just-in-Time Provisioning

Device generates key on first boot, submits CSR to registration service.

# Device generates key
openssl ecparam -genkey -name prime256v1 -out device.key

# Create CSR with device serial
openssl req -new -key device.key -out device.csr -subj "/CN=device-12345/serialNumber=12345"

# Submit to enrollment API
curl -X POST https://enroll.iot.example.com/csr \
  -H "Authorization: Bearer $ENROLL_TOKEN" \
  --data-binary @device.csr -o device.crt

3. AWS IoT Core

Register Device Certificate

# Generate key and CSR
openssl ecparam -genkey -name prime256v1 -out iot-device.key
openssl req -new -key iot-device.key -out iot-device.csr -subj "/CN=iot-device-001"

# Use AWS CLI to sign
aws iot create-certificate-from-csr --certificate-signing-request file://iot-device.csr \
  --set-as-active > cert-response.json

# Extract certificate
jq -r '.certificatePem' cert-response.json > iot-device.crt

Attach Policy

aws iot attach-policy --policy-name IoTDevicePolicy --target arn:aws:iot:region:account:cert/certId

MQTT Connect

import paho.mqtt.client as mqtt
import ssl

client = mqtt.Client()
client.tls_set(
    ca_certs="AmazonRootCA1.pem",
    certfile="iot-device.crt",
    keyfile="iot-device.key",
    tls_version=ssl.PROTOCOL_TLSv1_2
)
client.connect("xxxxxx.iot.us-east-1.amazonaws.com", 8883)
client.publish("device/telemetry", "{'temp': 22.5}")

4. Azure IoT Hub

X.509 Device Authentication

# Generate device cert signed by custom CA
openssl req -new -key device.key -out device.csr -subj "/CN=device-001"
openssl x509 -req -in device.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -out device.crt -days 365 -sha256

# Upload CA to Azure IoT Hub (portal or CLI)
az iot hub certificate create --hub-name MyIoTHub --name MyCACert --path ca.crt

# Connect device
from azure.iot.device import IoTHubDeviceClient

device_client = IoTHubDeviceClient.create_from_x509_certificate(
    hostname="MyIoTHub.azure-devices.net",
    device_id="device-001",
    x509=X509(cert_file="device.crt", key_file="device.key")
)
device_client.connect()
device_client.send_message("Hello from device-001")

5. Microchip ATECC608 Secure Element

Hardware crypto chip stores private keys that never leave the silicon.

Provisioning Flow

  1. Device generates key pair inside ATECC608.
  2. CSR created using on-chip key.
  3. CA signs CSR, certificate stored in device EEPROM.
// Arduino example with ATECC library
#include <ArduinoECCX08.h>

void setup() {
  ECCX08.begin();

  // Generate CSR
  byte csr[256];
  ECCX08.getCSR(csr);

  // Send CSR to CA via HTTP/MQTT
  // Receive signed certificate
  // Store in EEPROM
}

6. Certificate Rotation for Constrained Devices

Short-Lived Certificates

Issue 7-day certs with automated renewal via lightweight protocols like EST (RFC 7030).

# EST simple enroll
curl --cacert ca.crt --cert current-device.crt --key device.key \
  https://est.example.com/.well-known/est/simpleenroll \
  --data-binary @new-device.csr -o renewed-device.crt

Bootstrap Certificates

Initial long-lived cert used solely for enrollment, then replaced by short-lived operational certs.

7. LoRaWAN & Secure Join

LoRaWAN 1.1+ supports secure join with device-specific keys derived from certificates:

Device → JoinRequest (signed with DevEUI cert)
Network Server → JoinAccept (encrypted session keys)

8. Best Practices Summary

PracticeRationale
Use ECC (P-256, P-384)Smaller keys, lower power consumption
Hardware Root of TrustTPM, ATECC, or TrustZone prevent key extraction
Certificate Validity ≤ 1 yearLimit blast radius of compromise
Revocation via OCSP/CRLDisable compromised devices remotely
Separate PKI for IoTIsolate device CA from enterprise IT

9. Real-World Deployments

  • Automotive – Vehicle ECUs use certificates for V2X communication (IEEE 1609.2).
  • Industrial IoT – PLCs and SCADA devices authenticated via IEC 62351.
  • Smart Home – Matter protocol mandates X.509 certificates for device commissioning.

Security Tip: Never embed the same private key in multiple devices. Each device must have a unique certificate to enable selective revocation.

Appendix G: VPN Certificates

VPN Certificates — OpenVPN, WireGuard & IPsec

Virtual Private Networks use certificates to authenticate endpoints and establish encrypted tunnels.

1. OpenVPN with PKI

Setup Easy-RSA

git clone https://github.com/OpenVPN/easy-rsa.git
cd easy-rsa/easyrsa3
./easyrsa init-pki
./easyrsa build-ca nopass
./easyrsa gen-req server nopass
./easyrsa sign-req server server
./easyrsa gen-req client1 nopass
./easyrsa sign-req client client1
./easyrsa gen-dh
openvpn --genkey secret ta.key

Server Configuration

/etc/openvpn/server.conf:

port 1194
proto udp
dev tun

ca /etc/openvpn/pki/ca.crt
cert /etc/openvpn/pki/issued/server.crt
key /etc/openvpn/pki/private/server.key
dh /etc/openvpn/pki/dh.pem
tls-auth /etc/openvpn/ta.key 0

server 10.8.0.0 255.255.255.0
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 8.8.8.8"

cipher AES-256-GCM
auth SHA256
user nobody
group nobody
persist-key
persist-tun

status /var/log/openvpn-status.log
verb 3

Start:

sudo systemctl enable --now openvpn-server@server

Client Configuration

client1.ovpn:

client
dev tun
proto udp
remote vpn.example.com 1194

ca ca.crt
cert client1.crt
key client1.key
tls-auth ta.key 1

cipher AES-256-GCM
auth SHA256
verb 3

Connect:

sudo openvpn --config client1.ovpn

2. WireGuard (Certificate-less but Key-based)

WireGuard uses Curve25519 public keys instead of X.509 certificates. However, you can wrap WireGuard keys in certificates for enterprise identity management.

Generate Keys

wg genkey | tee privatekey | wg pubkey > publickey

Server Config

/etc/wireguard/wg0.conf:

[Interface]
PrivateKey = <server-private-key>
Address = 10.9.0.1/24
ListenPort = 51820

[Peer]
PublicKey = <client-public-key>
AllowedIPs = 10.9.0.2/32

Enable:

sudo systemctl enable --now wg-quick@wg0

Client Config

[Interface]
PrivateKey = <client-private-key>
Address = 10.9.0.2/24
DNS = 8.8.8.8

[Peer]
PublicKey = <server-public-key>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

Connect:

sudo wg-quick up wg0

3. IPsec (strongSwan) with X.509

Install strongSwan

sudo dnf install strongswan -y

Generate Certificates

# CA
ipsec pki --gen --type rsa --size 4096 --outform pem > ca.key.pem
ipsec pki --self --ca --lifetime 3650 --in ca.key.pem --type rsa \
  --dn "CN=VPN CA" --outform pem > ca.crt.pem

# Server cert
ipsec pki --gen --type rsa --size 2048 --outform pem > server.key.pem
ipsec pki --req --type priv --in server.key.pem --dn "CN=vpn.example.com" \
  --san vpn.example.com --outform pem > server.req.pem
ipsec pki --issue --cacert ca.crt.pem --cakey ca.key.pem --type pkcs10 \
  --in server.req.pem --lifetime 365 --outform pem > server.crt.pem

# Client cert
ipsec pki --gen --type rsa --size 2048 --outform pem > client.key.pem
ipsec pki --req --type priv --in client.key.pem --dn "CN=client@example.com" \
  --outform pem > client.req.pem
ipsec pki --issue --cacert ca.crt.pem --cakey ca.key.pem --type pkcs10 \
  --in client.req.pem --lifetime 365 --outform pem > client.crt.pem

Copy to /etc/ipsec.d/:

sudo cp ca.crt.pem /etc/ipsec.d/cacerts/
sudo cp server.crt.pem /etc/ipsec.d/certs/
sudo cp server.key.pem /etc/ipsec.d/private/

Server Configuration

/etc/ipsec.conf:

config setup
    charondebug="ike 2, knl 2, cfg 2"

conn %default
    ikelifetime=60m
    keylife=20m
    rekeymargin=3m
    keyingtries=1
    keyexchange=ikev2
    authby=pubkey

conn rw
    leftcert=server.crt.pem
    leftid=@vpn.example.com
    leftsubnet=0.0.0.0/0
    leftfirewall=yes
    right=%any
    rightid=%any
    rightsourceip=10.10.10.0/24
    auto=add

Start:

sudo systemctl enable --now strongswan

Client Configuration

Install client cert/key, then connect via NetworkManager or command-line.

4. Certificate Revocation in VPNs

OpenVPN CRL

./easyrsa revoke client1
./easyrsa gen-crl

Update server.conf:

crl-verify /etc/openvpn/pki/crl.pem

strongSwan OCSP

In /etc/ipsec.conf:

conn rw
    leftcert=server.crt.pem
    leftsendcert=always
    rightca="CN=VPN CA"
    rightcert=*
    ocsp_uri=http://ocsp.example.com

5. Comparison

VPNCertificate SupportKey ManagementUse Case
OpenVPNFull X.509 PKIEasy-RSA, manualEnterprise site-to-site & remote access
WireGuardPublic keys (no X.509 by default)Simple key pairsModern, high-performance, IoT
IPsec (strongSwan)Full X.509 PKIipsec pki toolsStandards-compliant, interop with Cisco/Juniper

6. Best Practices

  1. Separate CA for VPN – Isolate VPN certs from web TLS certs.
  2. Short Validity – Issue VPN client certs with 30-90 day expiry.
  3. CRL/OCSP – Enable revocation checking on the server.
  4. Hardware Tokens – Store client keys in YubiKeys or smartcards for remote workers.

Real-world: Many organisations migrate from OpenVPN to WireGuard for performance but wrap WireGuard keys in X.509 for integration with existing IAM systems.

Appendix H: Secure Boot and Certificate Usage

Secure Boot, Trust Chains, and Certificate Operations on RHEL

Secure Boot is where firmware, boot loaders, kernels, and kernel-loaded code stop being “just files on disk” and become authenticated code objects. If you understand TLS certificates but not Secure Boot, you are missing a large part of how trust actually starts on a modern system.

This appendix focuses on three things:

  1. What Secure Boot is actually doing.
  2. Where certificates and keys are used in the boot chain.
  3. How RHEL uses shim, GRUB, mokutil, pesign, kernel keyrings, and module signing in real deployments.

1. What Secure Boot Is

UEFI Secure Boot is a firmware-backed signature verification model for the boot path. The firmware checks whether an EFI executable is signed by a trusted key or certificate before it is allowed to run.

That means Secure Boot is not the same thing as:

  • Full-disk encryption
  • TPM-sealed secrets
  • Measured Boot
  • File Integrity Monitoring
  • User-space application whitelisting
  • TLS trust for web services

Secure Boot is specifically about authorizing code during the boot path and for kernel-space extensions such as loadable kernel modules.

In plain terms, the question Secure Boot asks is:

“Should this firmware component, EFI binary, boot loader, kernel, or module be trusted to execute?”

2. Why Certificates Matter in Secure Boot

Certificates are the transport mechanism for trust. They bind a public key to an identity or policy context so a verifier can decide whether a signature should be accepted.

In the Secure Boot ecosystem, certificates and keys are used in different places:

LocationPurpose
UEFI firmware variablesStore platform trust anchors and revocations
shimCarries embedded vendor trust for next-stage verification
Kernel keyringsHold trusted keys used to authenticate modules and related artifacts
MOK listAdds owner-controlled trust without rewriting firmware databases
Signature blocks on EFI binariesProve that boot components were signed by a trusted private key

This is a different trust domain from the RHEL TLS trust store in /etc/pki/ca-trust/. That trust store is for user-space PKI operations such as HTTPS, LDAPS, SMTP TLS, package retrieval, and application validation. It does not decide what EFI binary the firmware boots.

3. The UEFI Secure Boot Trust Model

3.1 Core Firmware Databases

UEFI Secure Boot commonly revolves around four important variable-backed databases:

DatabaseMeaningTypical Role
PKPlatform KeyTop-level owner of platform Secure Boot policy
KEKKey Exchange Key databaseAuthorizes updates to allowed and revoked signature databases
dbAllowed signatures / certificatesTrust list for EFI executables and drivers
dbxForbidden signatures / certificates / hashesRevocation list used to block known-bad binaries or certs

These are conceptually simple, but administrators routinely confuse them:

  • PK controls the platform’s Secure Boot authority.
  • KEK controls updates to the allowlist and denylist.
  • db says what is allowed to boot.
  • dbx says what must never be accepted, even if it was once trusted.

3.2 Setup Mode vs User Mode

UEFI firmware usually has different operational states:

  • Setup Mode: platform ownership is not finalized; key enrollment changes are possible.
  • User Mode: Secure Boot policy is actively enforced using installed keys.
  • Custom Mode: vendor-specific mode that may allow manual key management.

This matters because people often mistake “Secure Boot enabled in firmware menus” for “Secure Boot fully enforced with the expected trust policy.” Those are not always the same state.

4. What Actually Gets Signed

Secure Boot is not one signature over “the system.” It is a chain of separately signed or separately trusted objects.

Common authenticated objects include:

  • EFI applications
  • EFI boot loaders
  • GRUB EFI binaries
  • Linux kernels
  • Loadable kernel modules
  • Sometimes vendor firmware update executables

Just because a component participates in boot does not mean it is independently signed in the same way. For example:

  • EFI binaries are typically authenticated as PE/COFF executables with embedded signatures.
  • Kernel modules are signed in a Linux-specific way and verified by the kernel against trusted X.509 keys.
  • The initramfs is part of the boot flow, but in the classic RHEL Secure Boot model it is not simply “another independently PE-signed EFI executable.”

That distinction matters. Sloppy docs blur everything into “the whole boot chain is signed.” That is not precise enough to troubleshoot or design policy.

5. RHEL Secure Boot Chain of Trust

5.1 High-Level Flow

On a typical RHEL system with UEFI Secure Boot enabled:

  1. Firmware validates the first-stage EFI boot loader against trusted keys in firmware databases.
  2. The first-stage loader is typically shim.
  3. shim carries an embedded Red Hat trust anchor used to authenticate the next stage.
  4. shim validates the RHEL GRUB EFI binary.
  5. GRUB validates the kernel it loads using the public key embedded in shim (GRUB does not carry its own trust keys).
  6. The kernel uses its trusted keyrings to validate loadable kernel modules and related kernel-space code.

That gives you a chain of authorization from firmware to kernel-space extensibility.

5.2 Why shim Exists

shim exists because hardware vendors broadly ship systems with Microsoft-trusted UEFI signing roots already enrolled. Red Hat can therefore have the first-stage loader signed so it is accepted by commodity hardware without asking every hardware vendor to pre-install a Red Hat-only firmware trust anchor.

After firmware accepts shim, shim becomes the bridge between firmware trust and vendor OS trust.

In practice on RHEL:

  • Firmware trusts the Microsoft-recognized signature path for shim.
  • shim contains an embedded Red Hat Secure Boot CA certificate.
  • That embedded Red Hat certificate is used to validate GRUB and the kernel.

5.3 Why RHEL Does Not Rely on Arbitrary GRUB Module Loading

Under Secure Boot, RHEL does not want arbitrary unsigned code loaded inside the boot loader security boundary. Red Hat documentation explains that GRUB module loading is disabled in Secure Boot contexts because there is no broad-purpose signing and verification model for arbitrary GRUB modules equivalent to the controlled signed path Red Hat ships.

The operational point is simple:

  • If you are on a Secure Boot system, do not assume “GRUB can just load anything from disk.”
  • The whole design is trying to keep unsigned code out of the boot path.

6. Certificates and Keys Used by RHEL Secure Boot

6.1 Firmware Certificates

Firmware trust comes from keys and certificates stored in UEFI variables (PK, KEK, db, dbx) as described in section 3.1. These are not managed with update-ca-trust.

6.2 Embedded shim Certificate

Red Hat documentation for RHEL 8/9/10 describes shim as containing a Red Hat public certificate used to authenticate GRUB and the kernel. This embedded trust is one of the reasons shim is central to the RHEL Secure Boot design.

6.3 Machine Owner Key (MOK)

The Machine Owner Key facility is the practical escape hatch that keeps Secure Boot usable in the real world.

Without MOK, you would be trapped between two bad options:

  1. Disable Secure Boot whenever you need custom code.
  2. Convince your hardware vendor to permanently add your public certificate to firmware databases.

MOK provides a third option:

  • You enroll your own public certificate on the system.
  • shim and MokManager manage that enrollment.
  • On boot, the key is propagated into a kernel-trusted keyring so your signed custom components can be accepted.

6.4 Kernel Keyrings

On modern RHEL, kernel module signature verification relies on kernel keyrings, primarily:

KeyringRole
.builtin_trusted_keysBuilt-in trusted keys embedded into the kernel or loaded as part of the trusted boot path
.platformPlatform-derived trust, including keys sourced from Secure Boot databases and MOK
.blacklistRevoked keys and hashes that must be rejected

RHEL documentation for module signing explicitly notes:

  • module signatures are checked against trusted X.509 keys from .builtin_trusted_keys and .platform
  • revoked entries from the blacklist are excluded from verification
  • MOK keys propagate to .platform on Secure Boot-enabled boots

That means trust is additive, but revocation still wins.

7. What Secure Boot Does and Does Not Protect

7.1 What It Protects

Secure Boot is designed to reduce the chance that the system executes unauthorized boot-path code such as:

  • tampered EFI binaries
  • rogue boot loaders
  • modified kernels
  • unsigned or untrusted kernel modules

7.2 What It Does Not Automatically Solve

Secure Boot does not automatically protect:

  • user-space binaries
  • shell scripts
  • configuration files
  • secrets at rest
  • runtime memory tampering after a system is already compromised
  • TLS server identity for services
  • local privilege escalation through signed-but-vulnerable code

Secure Boot is a boot authorization control. It is not a complete host integrity framework.

7.3 Secure Boot vs Measured Boot vs TPM

People mash these together because they all touch startup trust. That is lazy thinking.

They are related, but different:

FeatureMain Function
Secure BootBlocks execution of unauthorized code in the boot path
Measured BootRecords boot measurements into TPM PCRs
TPMStores protected secrets and measurements; can seal data to boot state
IMA appraisalExtends integrity policy beyond early boot into file appraisal and runtime loading decisions

A practical RHEL implication:

  • Secure Boot decides whether code is accepted to boot or load.
  • TPM-based workflows may depend on PCR values that reflect Secure Boot policy and firmware database state.

If firmware keys or revocation databases change, TPM measurements can change as well. That matters for LUKS auto-unlock, attestation, and sealed-secret workflows.

8. Kernel Lockdown on RHEL

On RHEL, booting in EFI Secure Boot mode activates kernel lockdown behavior. This is important because Secure Boot alone is not enough if the running kernel still exposes interfaces that let privileged users tamper with kernel memory or bypass trust decisions.

Lockdown is intended to close that gap.

Typical restrictions include limits or outright blocks on things like:

  • loading unsigned modules
  • direct kernel image modification paths
  • direct access interfaces such as /dev/mem
  • some unsigned kexec paths
  • interfaces that could weaken the boot trust boundary

This is why admins sometimes see messages like:

Lockdown: X: Y is restricted; see man kernel_lockdown.7

That is not random kernel drama. It is Secure Boot policy following through into runtime enforcement.

9. The RHEL Administrator’s Mental Model

If you only keep one model in your head, keep this one:

  1. Firmware trusts keys in db and rejects keys or hashes in dbx.
  2. Firmware authorizes shim.
  3. shim authorizes Red Hat’s next-stage boot components and also supports MOK operations.
  4. MOK lets you add owner-controlled public certificates.
  5. The kernel trusts built-in and platform/MOK-derived keys for module verification.
  6. Lockdown prevents obvious runtime bypasses.

If any one of those steps is broken, your custom boot or module workflow fails.

10. Secure Boot on RHEL: Daily Operational Checks

10.1 Check Whether Secure Boot Is Enabled

sudo mokutil --sb-state

Typical output is along the lines of:

SecureBoot enabled

10.2 Check for Secure Boot and Integrity Messages in the Kernel Log

sudo dmesg | grep -Ei 'secure boot|integrity|lockdown|EFI: Loaded cert'

On RHEL systems, integrity log messages often show where keys came from, such as:

  • UEFI:db
  • embedded shim
  • UEFI:MokListRT

10.3 List Trusted Platform Keys

sudo keyctl list %:.platform
sudo keyctl list %:.builtin_trusted_keys
sudo keyctl list %:.blacklist

What you are looking for:

  • firmware-derived certificates
  • MOK-enrolled certificates
  • revoked hashes or keys in .blacklist

10.4 Inspect Signatures on EFI Binaries

On RHEL, pesign is the supported tool path for inspecting and adding signatures to relevant EFI binaries:

sudo pesign --show-signature --in /boot/efi/EFI/redhat/shimx64.efi
sudo pesign --show-signature --in /boot/efi/EFI/redhat/grubx64.efi

On AArch64 systems, the names commonly change to shimaa64.efi and grubaa64.efi.

11. Key RHEL Packages and Tools

When working with custom Secure Boot signing on RHEL 8/9/10, Red Hat documentation centers on these tools:

sudo dnf install pesign openssl kernel-devel mokutil keyutils

Main roles:

ToolPurpose
pesignSign and inspect EFI binaries and kernels
efikeygenGenerate a Secure Boot-oriented X.509 key pair in the pesign database
mokutilEnroll and inspect Machine Owner Keys and Secure Boot state
keyctlInspect kernel keyrings
sign-fileAppend Linux kernel module signatures
certutil / pk12utilExport keys and certificates from the NSS database used by pesign
opensslExtract or transform key material when needed

12. Generating a Custom Secure Boot Key on RHEL

12.1 Generate a Key for Module Signing

Red Hat documents efikeygen as the standard way to create a self-signed X.509 pair for Secure Boot workflows:

sudo efikeygen \
  --dbdir /etc/pki/pesign \
  --self-sign \
  --module \
  --common-name 'CN=Organization signing key' \
  --nickname 'Custom Secure Boot key'

12.2 Generate a Key for Kernel Signing

sudo efikeygen \
  --dbdir /etc/pki/pesign \
  --self-sign \
  --kernel \
  --common-name 'CN=Organization signing key' \
  --nickname 'Custom Secure Boot key'

12.3 FIPS-Aware Note

Red Hat documentation notes that in FIPS mode you may need to specify the NSS token explicitly:

sudo efikeygen \
  --dbdir /etc/pki/pesign \
  --self-sign \
  --kernel \
  --common-name 'CN=Organization signing key' \
  --nickname 'Custom Secure Boot key' \
  --token 'NSS FIPS 140-2 Certificate DB'

The generated material is stored under /etc/pki/pesign/.

13. Enrolling the Public Certificate with MOK

This is the step people skip, and then they waste hours blaming Secure Boot instead of their own process.

Generating a key is not enough. The target system must trust the corresponding public certificate.

13.1 Export the Public Certificate

sudo certutil -d /etc/pki/pesign \
  -n 'Custom Secure Boot key' \
  -Lr > sb_cert.cer

13.2 Import It into MOK

sudo mokutil --import sb_cert.cer

You will be prompted to set a temporary enrollment password.

13.3 Reboot and Complete Enrollment

On the next boot:

  1. shim notices the pending enrollment.
  2. MokManager.efi starts.
  3. You choose Enroll MOK.
  4. You enter the password you set during mokutil --import.
  5. The certificate is added to the persistent MOK list.

Once enrolled on a Secure Boot-enabled system, the key is propagated into the .platform keyring on subsequent boots.

14. Signing Custom Kernel Modules on RHEL

This is one of the most common real-world Secure Boot tasks. Out-of-tree drivers, vendor modules, HBA agents, monitoring probes, or security products often fail here.

14.1 Export the Public Certificate

Export the public certificate as described in section 13.1 to produce sb_cert.cer.

14.2 Export the Private Key from the NSS Database

sudo pk12util -o sb_cert.p12 \
  -n 'Custom Secure Boot key' \
  -d /etc/pki/pesign

Then extract the private key:

openssl pkcs12 \
  -in sb_cert.p12 \
  -out sb_cert.priv \
  -nocerts \
  -noenc

That produces an unencrypted private key. Treat that file like a live weapon, because that is what it is.

14.3 Sign the Module

sudo /usr/src/kernels/$(uname -r)/scripts/sign-file \
  sha256 \
  sb_cert.priv \
  sb_cert.cer \
  my_module.ko

This appends the module signature directly to the kernel module file.

14.4 Verify the Signer

modinfo my_module.ko | grep signer

14.5 Load the Module

sudo insmod my_module.ko

or after placing it under the module tree:

sudo cp my_module.ko /lib/modules/$(uname -r)/extra/
sudo depmod -a
sudo modprobe my_module

14.6 Validity-Date Operational Warning

Red Hat documentation warns administrators to sign kernels and modules within the certificate validity period and also notes that sign-file does not warn about bad timing decisions. Do not treat the absence of a tool warning as proof that your signing workflow is sane.

15. Signing Kernels and EFI Binaries on RHEL

15.1 Sign a Kernel on x86_64

sudo pesign \
  --certificate 'Custom Secure Boot key' \
  --in vmlinuz-version \
  --sign \
  --out vmlinuz-version.signed

Inspect the result:

sudo pesign --show-signature --in vmlinuz-version.signed

Replace the unsigned image with the signed one:

sudo mv vmlinuz-version.signed vmlinuz-version

If you skip this step, the system still boots the unsigned original.

15.2 Sign a GRUB EFI Binary on x86_64

sudo pesign \
  --in /boot/efi/EFI/redhat/grubx64.efi \
  --out /boot/efi/EFI/redhat/grubx64.efi.signed \
  --certificate 'Custom Secure Boot key' \
  --sign

Inspect the result:

sudo pesign --in /boot/efi/EFI/redhat/grubx64.efi.signed --show-signature

Replace the unsigned binary with the signed one:

sudo mv /boot/efi/EFI/redhat/grubx64.efi.signed /boot/efi/EFI/redhat/grubx64.efi

15.3 AArch64 Note

On AArch64 systems, you will typically work with:

  • /boot/efi/EFI/redhat/shimaa64.efi
  • /boot/efi/EFI/redhat/grubaa64.efi

RHEL documentation also covers the decompression/recompression workflow for signing kernel images on 64-bit ARM.

16. Where Secure Boot Trust Appears Inside the Running RHEL Kernel

RHEL documentation shows that a Secure Boot-enabled system can expose evidence of loaded trust sources in both kernel logs and keyrings.

Examples of what you may see:

  • Microsoft certificate entries sourced from UEFI db
  • Red Hat Secure Boot keys sourced from embedded shim
  • owner-enrolled keys sourced from MokListRT
  • revocations reflected in .blacklist

That gives you three different trust sources in play:

  1. Firmware trust
  2. Embedded vendor trust
  3. Owner-added trust

If you do not know which of the three your system is using for a given module or EFI binary, you are troubleshooting blind.

17. Secure Boot Revocation and dbx

Revocation is where a lot of “but it used to work” incidents come from.

The dbx database contains revoked signatures, certificates, or hashes. If an object chains to a revoked entry, the system rejects it even if it used to be trusted.

Operational consequences:

  • old boot loaders can stop working after revocation updates
  • vulnerable or deprecated signatures can become unacceptable
  • lab systems that never receive firmware updates drift into weird compatibility states
  • custom boot chains break if you anchored them to something that later lands in revocation data

Inside Linux, revoked entries are represented through the blacklist keyring. That is why trust alone is not enough; the object must also not be revoked.

18. Microsoft 2011 Secure Boot Certificate Expiration

The Microsoft UEFI CA 2011 signing certificate, which has been the primary trust anchor used by firmware to validate shim on virtually all x86_64 commodity hardware, is scheduled to expire on June 27, 2026.

This does not mean existing systems immediately stop booting. Systems that already have the 2011 certificate enrolled in firmware db will continue to accept binaries signed with that certificate after the expiration date. However, Microsoft will no longer sign new binaries with the 2011 key after expiration, so future shim updates must be signed with the replacement Microsoft UEFI CA 2023 certificate.

18.1 What Red Hat Has Done

Red Hat released new shim binaries for all supported RHEL 8, RHEL 9, and RHEL 10 releases on x86_64 that are dual-signed with both the Microsoft 2011 and Microsoft 2023 Secure Boot signing certificates. This means the new shim will boot on systems that have either or both certificates enrolled in firmware.

On AArch64, starting with RHEL 9.7 and RHEL 10.0, the shim binary is signed only with the Microsoft 2023 certificate.

18.2 Checking Which Certificate Signed Your shim

sudo pesign -S -i /boot/efi/EFI/redhat/shimx64.efi

If you see Microsoft Windows UEFI Driver Publisher, that is the 2011 certificate path. If you see references to the 2023 certificate, the system is using the updated signing path.

18.3 What Administrators Must Do

  1. Update shim on all supported RHEL systems to get the dual-signed version before relying on firmware updates that add the 2023 certificate.
  2. Watch for firmware updates from hardware vendors that enroll the Microsoft 2023 certificate into the UEFI db. Without the 2023 certificate in firmware, future shim binaries signed only with the 2023 key will not be accepted.
  3. Be aware of TPM impact: updates to UEFI db will change TPM Platform Configuration Register (PCR) values, particularly PCR7. If you use TPM-based auto-unlock for LUKS-encrypted volumes, Measured Boot attestation, or secrets sealed against PCR7, those bindings will break after db changes. The recommended approach is to first reseal against a PCR value that did not change (such as PCR0), reboot, and then reseal against the new PCR7 value.
  4. Legacy systems (old physical servers, appliances, or systems that never receive firmware updates) that cannot enroll the 2023 certificate will remain limited to booting shim binaries signed with the 2011 certificate.

18.4 Why This Matters for This Book

This is a concrete, real-world example of every concept this appendix covers: firmware trust databases, certificate lifecycle, revocation risk, TPM measurement sensitivity, and the operational cost of ignoring signing certificate management. If your organization did not know this was coming, your Secure Boot lifecycle management has a gap.

19. Secure Boot and RHEL Version Notes

19.1 RHEL 7

RHEL 7 established the basic Red Hat Secure Boot model:

  • shim as first-stage loader
  • Red Hat embedded key in shim
  • signed GRUB and kernel
  • MOK for owner-added trust
  • signed kernel modules required on Secure Boot-enabled systems

RHEL 7 documentation also makes an important point that many people miss: classic Secure Boot is about kernel-space code integrity, not blanket validation of all user-space content.

19.2 RHEL 8

RHEL 8 keeps the same overall model, with improved public documentation around:

  • .builtin_trusted_keys
  • .platform
  • .blacklist
  • efikeygen
  • pesign
  • MOK enrollment procedures

RHEL 8 docs also explicitly show that MOK-enrolled keys propagate to .platform.

19.3 RHEL 9

RHEL 9 documents the same core trust path and is stricter and clearer operationally around:

  • module signature validation against trusted keyrings
  • MOK-driven platform trust
  • signing custom kernels and modules
  • lockdown behavior on Secure Boot systems

RHEL 9 is the practical baseline if you are designing a modern RHEL Secure Boot workflow today.

19.4 RHEL 10

RHEL 10 documentation continues the same toolchain and trust model for custom kernel and module signing with pesign, mokutil, efikeygen, keyctl, and kernel keyrings.

The important point is continuity: this is not a random feature that changes shape every release. The tool names and trust concepts stay recognizable across supported RHEL generations.

20. Common RHEL Use Cases

20.1 Third-Party Driver Deployment

Common examples:

  • storage controller drivers
  • monitoring agents with kernel modules
  • endpoint security modules
  • custom network drivers
  • vendor hardware management modules

If the module is unsigned or signed by an untrusted key, the system refuses it on a Secure Boot-enabled host.

20.2 Custom Kernel Builds

If you build your own kernel, firmware and the boot chain do not care that it came from your CI pipeline. They only care whether the image is signed by a trusted key and whether the system has that trust anchor enrolled.

20.3 Environments Removing Vendor Trust Anchors

Some organizations want tighter control and reduce reliance on default third-party trust anchors. That is possible, but it means you own the full chain:

  • signing policy
  • private-key protection
  • firmware trust management
  • GRUB/kernel signing
  • revocation strategy
  • recovery path if your signing infrastructure fails

Most teams underestimate that operational burden.

20.4 Virtual Machines

Secure Boot also matters in virtualized environments if the guest firmware exposes UEFI Secure Boot support. Do not assume that “it is only a VM” means boot trust is irrelevant. Virtual infrastructure is one of the easiest places for unsigned custom images to proliferate.

21. Troubleshooting Secure Boot on RHEL

21.1 Fast Checks

sudo mokutil --sb-state
sudo mokutil --list-enrolled
sudo keyctl list %:.platform
sudo keyctl list %:.blacklist
sudo dmesg | grep -Ei 'secure boot|lockdown|integrity|module|cert'

21.2 Typical Failure Patterns

SymptomLikely Cause
Module will not loadUnsigned module or signer not trusted
Module shows signer but still failsKey not enrolled on target system, wrong keyring path, or revoked signer
MOK import ran but trust not visibleEnrollment not completed in MokManager after reboot
EFI binary fails to bootMissing trusted signature, bad replacement workflow, or revoked certificate/hash
Behavior changed after firmware updatedb/dbx changes altered trust or revocation state
TPM-unlock workflow breaks after key updatesPCR measurements changed after Secure Boot database updates

21.3 Kernel Log Clues

Look for messages mentioning:

  • integrity
  • MokListRT
  • EFI: Loaded cert
  • Lockdown
  • module verification failed

If you do not inspect the kernel log, you are guessing.

22. Best Practices for Secure Boot Certificate Management

22.1 Separate Roles

Do not use one signing key for everything unless you enjoy turning one compromise into a platform-wide incident.

Prefer separate keys for:

  • boot loaders / EFI binaries
  • kernels
  • third-party or internal kernel modules
  • emergency or break-glass workflows

22.2 Protect the Private Key Properly

Prefer:

  • offline signing
  • HSM-backed or token-backed key storage where possible
  • minimal operator access
  • audited signing steps
  • certificate rotation planning

Avoid:

  • leaving unencrypted extracted keys on build hosts
  • copying signing material across ad hoc CI jobs
  • long-lived throwaway lab keys reused in production

22.3 Keep MOK Enrollment Narrow

Every extra trusted certificate expands what the platform can accept. Enroll only what you need. “Just add another MOK to get it working” is how trust sprawl starts.

22.4 Track Revocation and Firmware Updates

If you depend on custom Secure Boot trust:

  • monitor firmware and dbx updates
  • test recovery paths before broad rollout
  • validate boot on staging hardware
  • understand the impact on TPM-sealed secrets and attestation

22.5 Keep Secure Boot and TLS PKI Mentally Separate

The same X.509 concepts appear in both areas, but the operational worlds are different.

Do not confuse:

  • firmware trust databases with /etc/pki/ca-trust
  • MOK enrollment with CA trust anchor installation
  • module signing keys with web TLS server certificates

They use related cryptography, but they solve different problems.

23. Hard Truths Administrators Usually Learn Late

  1. Secure Boot is easy until you need custom code.
  2. Custom code is easy until you need durable signing operations.
  3. Durable signing operations are easy until you need revocation, rotation, attestation, and fleet recovery.

Most teams do not fail Secure Boot because the cryptography is too hard. They fail because their operational model is sloppy:

  • no key ownership model
  • no certificate lifecycle planning
  • no test hardware
  • no rollback path
  • no idea what is trusted by firmware vs shim vs MOK vs kernel keyrings

That is not a technical limitation. That is process failure.

24. Quick Reference

┌──────────────────────────────────────────────────────────────────────┐
│ RHEL SECURE BOOT QUICK REFERENCE                                     │
├──────────────────────────────────────────────────────────────────────┤
│ Check state:        mokutil --sb-state                               │
│ List MOKs:          mokutil --list-enrolled                          │
│ List platform keys: keyctl list %:.platform                          │
│ Built-in keys:      keyctl list %:.builtin_trusted_keys              │
│ Revocations:        keyctl list %:.blacklist                         │
│ View EFI signature: pesign --show-signature --in <efi-binary>        │
│                                                                      │
│ RHEL boot path:     firmware -> shim -> GRUB -> kernel -> modules    │
│                                                                      │
│ Firmware DBs:       PK / KEK / db / dbx                              │
│ Owner trust:        MOK -> .platform                                 │
│ Kernel trust:       .builtin_trusted_keys + .platform                │
│ Revocation:         .blacklist / dbx                                 │
│                                                                      │
│ Common packages:    pesign openssl kernel-devel mokutil keyutils     │
└──────────────────────────────────────────────────────────────────────┘

25. Key Takeaways

  1. Secure Boot is a certificate-driven authorization chain for boot and kernel-space code.
  2. On RHEL, shim is the bridge between firmware trust and Red Hat-controlled trust.
  3. MOK is the critical mechanism for adding owner-controlled trust without rewriting firmware databases.
  4. Kernel module loading depends on trusted keyrings, not on the user-space CA bundle.
  5. Revocation matters as much as trust; dbx and .blacklist can break “previously working” artifacts.
  6. If you are signing custom modules or kernels, the problem is not just cryptography. It is lifecycle management.

26. Official RHEL Reading Worth Keeping Nearby

  • RHEL 8/9/10 kernel management documentation on signing kernels and modules for Secure Boot
  • RHEL 7 kernel administration and Secure Boot guidance
  • kernel_lockdown(7)
  • mokutil(1)
  • keyctl(1)
  • pesign(1)

Appendix I: Glossary

Glossary of PKI & Certificate Terms

A

ACME (Automated Certificate Management Environment) Protocol (RFC 8555) for automated certificate issuance and renewal, popularised by Let’s Encrypt.

ASN.1 (Abstract Syntax Notation One) Data serialisation format used to encode X.509 certificates.

Asymmetric Cryptography Public-key system where encryption/decryption use complementary key pairs (public and private).

B

Baseline Requirements CAB Forum rules that publicly-trusted CAs must follow (e.g., 398-day maximum validity).

C

CA (Certificate Authority) Entity that issues and signs digital certificates.

CAB Forum (CA/Browser Forum) Industry consortium defining standards for publicly-trusted TLS certificates.

Certificate Chain Sequence of certificates from end-entity -> intermediate(s) -> root CA.

Certificate Policy (CP) High-level document describing what assurances a PKI provides.

Certificate Revocation List (CRL) Signed list of revoked certificate serial numbers.

Certificate Signing Request (CSR) Message requesting a CA to sign a public key, includes subject DN and extensions.

Certificate Transparency (CT) Public log system (RFC 6962) that records all issued certificates for auditability.

Certification Practice Statement (CPS) Detailed operational procedures of how a CA implements its CP.

Cipher Suite Set of cryptographic algorithms used in TLS (e.g., key exchange, encryption, MAC).

CN (Common Name) Field in certificate subject; historically held domain name, now superseded by SAN.

Code Signing Certificate Certificate with extKeyUsage: codeSigning for authenticating software publishers.

D

DER (Distinguished Encoding Rules) Binary encoding of ASN.1, used for certificates in Java keystores and embedded systems.

DH (Diffie-Hellman) Key-exchange algorithm allowing two parties to agree on a shared secret over insecure channel.

DN (Distinguished Name) Hierarchical identifier in X.500 format (e.g., /C=US/O=Example/CN=server.example.com).

E

ECC (Elliptic Curve Cryptography) Public-key crypto using elliptic curves; offers smaller keys than RSA for equivalent security.

ECDHE (Elliptic Curve Diffie-Hellman Ephemeral) Key-exchange providing forward secrecy in TLS.

ECDSA (Elliptic Curve Digital Signature Algorithm) Signature scheme based on elliptic curves.

EKU (Extended Key Usage) X.509 extension specifying allowed certificate purposes (serverAuth, clientAuth, codeSigning, etc.).

End-Entity Certificate Leaf certificate issued to servers, devices, or users (not a CA).

EST (Enrollment over Secure Transport) RFC 7030 protocol for certificate enrollment, simpler than full SCEP.

F

FIPS 140-2 U.S. government standard for cryptographic module security (Levels 1-5).

Forward Secrecy Property ensuring past session keys remain secure even if long-term private key is compromised (achieved via ephemeral DH/ECDHE).

H

HSM (Hardware Security Module) Tamper-resistant device storing cryptographic keys and performing operations in hardware.

HSTS (HTTP Strict Transport Security) Header forcing browsers to use HTTPS for a domain.

I

Intermediate CA CA certificate signed by root CA, used to issue end-entity certificates.

Issuer DN of the CA that signed a certificate.

J

JWKS (JSON Web Key Set) Set of public keys in JSON format, often used with OAuth/OpenID Connect.

JWT (JSON Web Token) Compact token format for transmitting claims; can be signed with RSA/ECDSA.

K

Key Escrow Practice of storing private keys with a third party; controversial for user privacy.

Key Usage X.509 extension defining permitted cryptographic operations (digitalSignature, keyEncipherment, etc.).

Keystore File storing private keys and certificates (e.g., Java JKS, PKCS#12).

L

LDAP (Lightweight Directory Access Protocol) Protocol for accessing directory services; often used to publish certificates and CRLs.

M

mTLS (Mutual TLS) TLS where both client and server present certificates for bidirectional authentication.

N

NSS (Network Security Services) Mozilla’s crypto library used by Firefox; maintains its own trust store.

O

OCSP (Online Certificate Status Protocol) Real-time protocol (RFC 6960) for checking certificate revocation status.

OCSP Stapling Server includes fresh OCSP response in TLS handshake, improving privacy and performance.

OID (Object Identifier) Unique numeric identifier in ASN.1 (e.g., 2.5.29.17 for SAN extension).

P

PEM (Privacy Enhanced Mail) Base64-encoded DER with -----BEGIN CERTIFICATE----- headers.

PFX See PKCS#12.

PIN (Public Key Pinning) Mechanism to associate domain with specific public key(s); deprecated in browsers due to operational risks.

PKI (Public Key Infrastructure) System of hardware, software, policies, and procedures for managing digital certificates.

PKCS (Public-Key Cryptography Standards) Family of standards by RSA Labs (PKCS#1-15).

PKCS#7 Container format for certificates and CRLs (no private key).

PKCS#12 Archive format bundling certificate + private key + chain, password-protected (.p12, .pfx).

R

RA (Registration Authority) Entity validating identity before forwarding CSR to CA.

Root CA Self-signed CA at top of hierarchy; embedded in OS/browser trust stores.

RSA (Rivest-Shamir-Adleman) Widely-used public-key algorithm based on integer factorisation.

S

SAN (Subject Alternative Name) X.509 extension listing additional identities (DNS names, IPs, URIs).

SCT (Signed Certificate Timestamp) Proof from CT log that certificate has been logged.

Self-Signed Certificate Certificate where issuer == subject; not trusted by default.

Serial Number Unique identifier assigned by CA to each certificate.

SHA-256 / SHA-384 Cryptographic hash functions (part of SHA-2 family).

SPIFFE (Secure Production Identity Framework For Everyone) Standard for workload identity in dynamic environments; uses URI SANs (spiffe://trust-domain/workload).

SSL (Secure Sockets Layer) Deprecated predecessor to TLS.

T

TLS (Transport Layer Security) Cryptographic protocol for secure network communication (current versions: 1.2, 1.3).

TPM (Trusted Platform Module) Hardware chip for secure key storage on devices.

Trust Anchor Root certificate implicitly trusted by a system.

Trust Store Collection of trusted root CA certificates (e.g., /etc/pki/ca-trust, Windows Certificate Store).

V

VA (Validation Authority) Component handling OCSP/CRL queries.

W

Wildcard Certificate Certificate covering *.example.com (matches app.example.com, not sub.app.example.com).

X

X.509 ITU-T standard defining certificate format (RFC 5280 is IETF version).

Z

Zero Trust Security model assuming no implicit trust; every request authenticated and authorised.

Appendix J: References

References & Further Reading

Curated list of authoritative resources for deepening your PKI and certificate knowledge.

Standards & RFCs

Core PKI Standards

TLS/SSL

Cryptographic Algorithms

Industry Guidelines

CA/Browser Forum

NIST Publications

ETSI Standards

Books

Foundational

  • “Network Security with OpenSSL” by Pravir Chandra, Matt Messier, John Viega O’Reilly, 2002 - Comprehensive OpenSSL guide

  • “PKI Uncovered” by Andre Karamanian, Siva Sathianathan Cisco Press, 2011 - Enterprise PKI design patterns

  • “Bulletproof SSL and TLS” by Ivan Ristic Feisty Duck, 2022 - Authoritative guide to deploying TLS correctly

Advanced

  • “Serious Cryptography” by Jean-Philippe Aumasson No Starch Press, 2017 - Modern cryptographic algorithms and protocols

  • “Applied Cryptography” by Bruce Schneier Wiley, 1996 - Classic text on cryptographic protocols

Online Resources

Documentation

Testing Tools

Tutorials & Blogs

Videos & Courses

  • “Public Key Cryptography” (Khan Academy) Introduction to RSA and key exchange

  • “How HTTPS Works” (Cloudflare YouTube) Animated explanation of TLS handshake

  • Pluralsight - “PKI Architecture and Implementation” Comprehensive video course on enterprise PKI

Software & Tools

CA Software

Certificate Management

Libraries

Communities & Forums

Research Papers

  • “The Most Dangerous Code in the World” (Martin et al., 2012) Analysis of SSL certificate validation vulnerabilities

  • “Analysis of the HTTPS Certificate Ecosystem” (Durumeric et al., IMC 2013) Large-scale study of TLS deployment

  • “SoK: SSL and HTTPS Revisiting past challenges and evaluating certificate trust model enhancements” (Clark & van Oorschot, S&P 2013)

Compliance Frameworks


Stay Updated: PKI and TLS standards evolve continuously. Subscribe to the IETF TLS working group mailing list and follow security advisories from your CA and software vendors.