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

Appendix I: Crypto-Policies — Internal Architecture, Subpolicies, and Complete Parameter Reference

Scope: This appendix documents the internal workings of the RHEL 8/9/10 crypto-policies framework — from policy definition to the generation of configuration files for each back-end. It covers all parameters of the policy language, the scope system, the creation of .pmod subpolicies and .pol full policies, the quirks of each back-end, and the actual flow of update-crypto-policies.


I.1 Why This Appendix Exists

Chapters 10, 23, and 31 of this tutorial present what crypto-policies do, how to switch between them, and how to diagnose problems. This appendix goes further: it describes how the system works internally, the exact syntax accepted in .pol and .pmod files, which parameters exist, how each one affects each back-end, and which concrete pitfalls await the administrator who creates custom policies.


I.2 Internal Architecture — The Complete Pipeline

The update-crypto-policies command is a shell wrapper (/usr/bin/update-crypto-policies) that invokes the Python script update-crypto-policies.py. All actual processing occurs in Python, using the cryptopolicies (policy parsing) and policygenerators (back-end generation) modules.

When the administrator runs update-crypto-policies --set DEFAULT:NO-SHA1, the system goes through the following steps:

┌──────────────────────────────────────────────────────────────────┐
│  1. READING THE BASE POLICY                                      │
│     The UnscopedCryptoPolicy class looks for DEFAULT.pol in      │
│     this order:                                                  │
│       1. Current directory                                       │
│       2. policies/ (relative)                                    │
│       3. /etc/crypto-policies/policies/                          │
│       4. /usr/share/crypto-policies/policies/                    │
│     The first file found is used.                                │
└──────────────────────┬───────────────────────────────────────────┘
                       │
                       ▼
┌──────────────────────────────────────────────────────────────────┐
│  2. READING AND CONCATENATING SUBPOLICIES                        │
│     Looks for NO-SHA1.pmod in modules/ in the same paths above.  │
│                                                                  │
│     The directives from DEFAULT.pol and NO-SHA1.pmod are         │
│     concatenated into a sequential list of Directive(prop_name,  │
│     scope, operation, value) objects.                            │
└──────────────────────┬───────────────────────────────────────────┘
                       │
                       ▼
┌──────────────────────────────────────────────────────────────────┐
│  3. PREPROCESSING (preprocess_text)                              │
│     Before parsing, the raw text goes through:                   │
│     a) Comment removal (#)                                       │
│     b) Line continuation resolution (\)                          │
│     c) Whitespace normalization                                  │
│     d) Conversion of deprecated parameters to modern equivalents │
│        (e.g.: min_tls_version=TLS1.2 →                           │
│        protocol@TLS = -SSL2.0 -SSL3.0 -TLS1.0 -TLS1.1)           │
│     e) Value renaming (e.g.: X25519-MLKEM768 →                   │
│        MLKEM768-X25519)                                          │
└──────────────────────┬───────────────────────────────────────────┘
                       │
                       ▼
┌──────────────────────────────────────────────────────────────────┐
│  4. PARSING (parse_line → parse_rhs)                             │
│     Each line is converted into Directives with operations:      │
│     - RESET:    "cipher ="          → clears the list            │
│     - APPEND:   "cipher = AES-*"    → adds to the end            │
│     - PREPEND:  "cipher = +AES-*"   → inserts at the beginning   │
│     - OMIT:     "cipher = -RC4-*"   → removes from the list      │
│     - SET_INT:  "min_rsa_size=2048" → sets integer               │
│     - SET_ENUM: "__ems = ENFORCE"   → sets enumeration           │
│     Wildcards (*) are expanded against the known algorithm       │
│     lists (alg_lists.py).                                        │
└──────────────────────┬───────────────────────────────────────────┘
                       │
                       ▼
┌──────────────────────────────────────────────────────────────────┐
│  5. SCOPED RESOLUTION (ScopedPolicy)                             │
│     For each back-end, the list of Directives is evaluated with  │
│     the relevant scopes of that back-end. A directive only takes │
│     effect if its ScopeSelector matches the back-end's scopes.   │
│     E.g.: cipher@SSH only affects {'ssh','openssh'}.             │
│     The result is lists of algorithms .enabled and .disabled     │
│     plus .integers and .enums values.                            │
└──────────────────────┬───────────────────────────────────────────┘
                       │
                       ▼
┌──────────────────────────────────────────────────────────────────┐
│  6. BACK-END GENERATION (PolicyGenerators)                       │
│     13 generator classes convert the ScopedPolicy into configs:  │
│                                                                  │
│     ┌─────────────────────────┬────────────────────────────┐     │
│     │ Generator Class         │ .config File               │     │
│     ├─────────────────────────┼────────────────────────────┤     │
│     │ OpenSSLGenerator        │ opensslcnf.config          │     │
│     │ OpenSSLFIPSGenerator    │ openssl_fips.config        │     │
│     │ GnuTLSGenerator         │ gnutls.config              │     │
│     │ NSSGenerator            │ nss.config                 │     │
│     │ OpenSSHClientGenerator  │ openssh.config             │     │
│     │ OpenSSHServerGenerator  │ opensshserver.config       │     │
│     │ LibsshGenerator         │ libssh.config              │     │
│     │ KRB5Generator           │ krb5.config                │     │
│     │ LibreswanGenerator      │ libreswan.config           │     │
│     │ BindGenerator           │ bind.config                │     │
│     │ JavaGenerator           │ java.config                │     │
│     │ SequoiaGenerator        │ sequoia.config             │     │
│     │ RPMSequoiaGenerator     │ rpm-sequoia.config         │     │
│     └─────────────────────────┴────────────────────────────┘     │
│     Each generator translates the generic algorithm names to     │
│     the library-specific names using mapping tables              │
│     (cipher_map, sign_map, group_map, etc.).                     │
│     Each generator also TESTS the generated config by running    │
│     the library's actual binary (openssl ciphers, gnutls-cli -l, │
│     ssh -G, sshd -T) to validate there are no errors.            │
└──────────────────────┬───────────────────────────────────────────┘
                       │
                       ▼
┌──────────────────────────────────────────────────────────────────┐
│  4. BACK-END GENERATION                                          │
│     For each supported library/application, a specific           │
│     configuration generator (policy generator) converts the      │
│     effective policy into a format the library understands:      │
│                                                                  │
│     ┌────────────┬──────────────────────────────────────────┐    │
│     │ Back-end   │ Generated File                           │    │
│     ├────────────┼──────────────────────────────────────────┤    │
│     │ OpenSSL    │ opensslcnf.config                        │    │
│     │ GnuTLS     │ gnutls.config                            │    │
│     │ NSS        │ nss.config                               │    │
│     │ OpenSSH    │ openssh.config, opensshserver.config     │    │
│     │ libssh     │ libssh.config                            │    │
│     │ Kerberos   │ krb5.config                              │    │
│     │ Libreswan  │ libreswan.config                         │    │
│     │ BIND       │ bind.config                              │    │
│     │ Java/JDK   │ java.config                              │    │
│     │ Sequoia    │ sequoia.config                           │    │
│     │ RPM-Seqoia │ rpm-sequoia.config                       │    │
│     └────────────┴──────────────────────────────────────────┘    │
└──────────────────────┬───────────────────────────────────────────┘
                       │
                       ▼
┌──────────────────────────────────────────────────────────────────┐
│  7. BACK-END INSTALLATION                                        │
│     The generated files are atomically written to:               │
│       /etc/crypto-policies/back-ends/                            │
│     (via mkstemp + os.rename to prevent corruption)              │
│                                                                  │
│     If the policy does not use subpolicies AND no local.d/       │
│     exists, a SYMLINK is created to the pre-generated back-ends  │
│     in /usr/share/crypto-policies/<POLICY>/ (optimization).      │
│                                                                  │
│     If content exists in /etc/crypto-policies/local.d/,          │
│     it is CONCATENATED to the end of the corresponding back-end. │
│                                                                  │
│     The file /etc/crypto-policies/config is updated.             │
│     The file /etc/crypto-policies/state/CURRENT.pol receives     │
│     the human-readable dump of the expanded effective policy.    │
│     The file /etc/crypto-policies/state/current receives the     │
│     name of the active policy (e.g.: "DEFAULT:NO-SHA1").         │
└──────────────────────┬───────────────────────────────────────────┘
                       │
                       ▼
┌──────────────────────────────────────────────────────────────────┐
│  8. SERVICE RELOAD                                               │
│     If --no-reload was NOT used, the script executes             │
│     /usr/share/crypto-policies/reload-cmds.sh which contains     │
│     reload commands for each affected service.                   │
│     Example: "systemctl try-restart sshd.service" for SSH.       │
│     Other libraries (OpenSSL, GnuTLS, NSS) only apply            │
│     the new policy when the application is restarted.            │
└──────────────────────────────────────────────────────────────────┘

Critical point: Applications (Apache, NGINX, Postfix, etc.) do not read the policy directly. They use libraries (OpenSSL, GnuTLS, NSS) which in turn read the back-end files. The policy change only takes effect when the process using the library is restarted.


I.3 Complete File Hierarchy

/usr/share/crypto-policies/
├── policies/                          # Package base policies
│   ├── DEFAULT.pol
│   ├── LEGACY.pol
│   ├── FUTURE.pol
│   ├── FIPS.pol
│   ├── BSI.pol
│   ├── EMPTY.pol
│   ├── NEXT.pol                       # Alias for DEFAULT
│   └── modules/                       # Package subpolicies
│       ├── AD-SUPPORT.pmod
│       ├── NO-SHA1.pmod
│       ├── NO-CAMELLIA.pmod
│       ├── NO-ENFORCE-EMS.pmod
│       ├── GOST.pmod
│       └── ...
├── DEFAULT/                           # Pre-generated back-ends for DEFAULT
│   ├── opensslcnf.config
│   ├── gnutls.config
│   ├── nss.config
│   └── ...
├── LEGACY/                            # Pre-generated back-ends for LEGACY
│   └── ...
├── FUTURE/                            # Pre-generated back-ends for FUTURE
│   └── ...
└── FIPS/                              # Pre-generated back-ends for FIPS
    └── ...

/etc/crypto-policies/
├── config                             # Text with name of the active policy
│                                      # E.g.: "DEFAULT:NO-SHA1"
├── back-ends/                         # Active back-ends (links or copies)
│   ├── opensslcnf.config
│   ├── openssl_fips.config            # OpenSSL FIPS module configuration
│   ├── gnutls.config
│   ├── nss.config
│   ├── openssh.config
│   ├── opensshserver.config
│   ├── libssh.config
│   ├── krb5.config
│   ├── libreswan.config
│   ├── bind.config
│   ├── java.config
│   ├── sequoia.config
│   └── rpm-sequoia.config
├── local.d/                           # Local per-back-end overrides
│   ├── opensslcnf-extra.config        # Concatenated to opensslcnf.config
│   ├── gnutls-extra.config
│   └── ...
├── state/
│   └── current -> /usr/share/crypto-policies/DEFAULT  # Symbolic link
├── policies/                          # Local custom policies
│   ├── MYPOLICY.pol                   # Full custom policy
│   └── modules/                       # Local custom subpolicies
│       └── MY-MODULE.pmod
└── state/
    └── CURRENT.pol                    # Expanded effective policy

Search priority: When update-crypto-policies looks for a .pol or .pmod file, it checks /etc/crypto-policies/policies/ first and then /usr/share/crypto-policies/policies/. This allows overriding package policies locally.


I.4 Policy Definition Format — Complete Syntax

The .pol and .pmod files use a simple INI-like syntax: key = value.

I.4.1 General Syntax Rules

# Comments start with '#'
# Everything after '#' on the line is ignored

# Line continuation with '\'
cipher = AES-256-GCM AES-128-GCM \
         CHACHA20-POLY1305

# Direct assignment — sets the complete list
cipher = AES-256-GCM AES-128-GCM CHACHA20-POLY1305

# Incremental modification — adds and removes from the existing list
# '+' at the beginning = prepend (insert at the start of the list, higher priority)
# no prefix at the end = append (insert at the end of the list)
# '-' = remove from the list
cipher = +AES-256-GCM -CAMELLIA-256-GCM AES-128-CCM+

# Empty list — disables everything in this category
cipher =

# Integer values
min_rsa_size = 3072

# Boolean values (0 or 1)
sha1_in_certs = 0

Fundamental rule of incremental modification:

Prefix/SuffixMeaningExample
+VALUE (prefix +)Prepend — inserts at the beginning of the list (higher priority)cipher = +AES-256-GCM
VALUE+ (suffix +)Append — inserts at the end of the list (lower priority)cipher = AES-128-CCM+
-VALUE (prefix -)Remove — takes out of the listcipher = -CAMELLIA-256-GCM
VALUE (no prefix, direct assignment)Reset — replaces the entire listcipher = AES-256-GCM AES-128-GCM

Direct assignment and incremental modification CANNOT be combined in the same directive.

I.4.2 Wildcards

The * character serves as a wildcard for matching algorithm names:

# Remove ALL SHA1 signature algorithms
sign = -*-SHA1

# Remove all AES-128 ciphers
cipher = -AES-128-*

# Remove all Camellia ciphers
cipher = -CAMELLIA-*

Caution: Wildcards in addition directives (+) may enable future algorithms that do not yet exist in the current version. Use wildcards with - (removal) for safety.

I.4.3 Scopes (@scope) — Per-Back-end Directives

Scopes allow a directive to affect only specific back-ends:

# Syntax: option@scope = value

# SSH only
cipher@SSH = -AES-128-CBC

# TLS only
protocol@TLS = TLS1.3 TLS1.2

# IKE (IPsec) only
protocol@IKE = IKEv2

# Multiple scopes
cipher@{SSH,TLS} = -CAMELLIA-*

# Scope negation — applies to all EXCEPT the specified one
cipher@!SSH = -AES-128-CBC

# Negation with multiple scopes
cipher@!{SSH,Kerberos} = -AES-128-CBC

Available scopes (complete list extracted from source code, ALL_SCOPES):

ScopeAffected Back-endsNotes
tls, sslOpenSSL, GnuTLS, NSS (nss-tls), JavaSynonyms
opensslOpenSSL onlyMore specific than tls
gnutlsGnuTLS onlyMore specific than tls
java-tlsJava/OpenJDK onlyMore specific than tls
nssNSS (all sub-scopes)NSS root scope
nss-tlsNSS for TLSInherits from nss
sshOpenSSH (client+server), libsshGeneric SSH scope
opensshOpenSSH (client+server)More specific than ssh
openssh-clientOpenSSH client onlyInherits from openssh
openssh-serverOpenSSH server onlyInherits from openssh
libsshlibssh onlyMore specific than ssh
ipsec, ikeLibreswanSynonyms
libreswanLibreswan onlyMore specific than ipsec
kerberos, krb5MIT KerberosSynonyms
dnssec, bindBINDSynonyms
sequoiaSequoia PGPOpenPGP
rpm, rpm-sequoiaRPM via SequoiaRPM package verification
pkcs12NSS — PKCS#12Inherits from nss
pkcs12-importNSS — PKCS#12 importInherits from nss-pkcs12
nss-pkcs12NSS — PKCS#12Functional synonym
nss-pkcs12-importNSS — PKCS#12 importMore permissive
smimeNSS — S/MIMEInherits from nss
smime-importNSS — S/MIME importInherits from nss-smime
nss-smimeNSS — S/MIMEFunctional synonym
nss-smime-importNSS — S/MIME importMore permissive

Scope selectors are case-insensitive. They support globbing (*) and negation (!). Multiple scopes in braces: @{SSH,TLS}. Multiple negation: @!{SSH,Kerberos}.

Inheritance hierarchy: See section I.13 for the complete scope hierarchy with parent-child relationships.


I.5 Complete Parameter Reference

I.5.1 List Parameters (multiple values)

cipher — Symmetric Ciphers

Defines which symmetric encryption algorithms (and modes of operation) are allowed.

cipher = AES-256-GCM AES-256-CCM AES-256-CBC \
         AES-128-GCM AES-128-CCM AES-128-CBC \
         CHACHA20-POLY1305

Recognized values:

ValueDescriptionKey Size
AES-256-GCMAES 256-bit, Galois/Counter mode (authenticated)256 bits
AES-256-CCMAES 256-bit, Counter with CBC-MAC mode256 bits
AES-256-CBCAES 256-bit, Cipher Block Chaining mode256 bits
AES-256-CTRAES 256-bit, Counter mode256 bits
AES-128-GCMAES 128-bit, GCM mode128 bits
AES-128-CCMAES 128-bit, CCM mode128 bits
AES-128-CBCAES 128-bit, CBC mode128 bits
AES-128-CTRAES 128-bit, CTR mode128 bits
CHACHA20-POLY1305ChaCha20 with Poly1305 AEAD256 bits
CAMELLIA-256-GCMCamellia 256-bit, GCM mode256 bits
CAMELLIA-256-CBCCamellia 256-bit, CBC mode256 bits
CAMELLIA-128-GCMCamellia 128-bit, GCM mode128 bits
CAMELLIA-128-CBCCamellia 128-bit, CBC mode128 bits
AES-192-GCMAES 192-bit, GCM mode192 bits
AES-192-CCMAES 192-bit, CCM mode192 bits
AES-192-CBCAES 192-bit, CBC mode192 bits
AES-192-CTRAES 192-bit, CTR mode192 bits
AES-256-OCBAES 256-bit, OCB mode (Sequoia)256 bits
AES-128-OCBAES 128-bit, OCB mode (Sequoia)128 bits
AES-256-EAXAES 256-bit, EAX mode (Sequoia)256 bits
AES-128-EAXAES 128-bit, EAX mode (Sequoia)128 bits
AES-256-CFBAES 256-bit, CFB mode (Sequoia/RPM)256 bits
AES-128-CFBAES 128-bit, CFB mode (Sequoia/RPM)128 bits
3DES-CBCTriple DES, CBC mode168 bits (effective: 112)
RC4-128RC4 stream cipher128 bits
RC4-40RC4 with 40-bit key40 bits
RC2-CBCRC2 CBC modeVariable
DES-CBCDES CBC mode56 bits
DES40-CBCDES CBC mode with 40-bit key40 bits
IDEA-CBCIDEA CBC mode128 bits
SEED-CBCSEED CBC mode128 bits
NULLNo encryption (integrity only)0 bits

Note: Algorithms like AES-*-OCB, AES-*-EAX, and AES-*-CFB are primarily used by the Sequoia and RPM (OpenPGP) back-ends. Algorithms like DES-CBC, RC4-40, RC2-CBC, DES40-CBC, IDEA-CBC, and SEED-CBC exist only to support legacy PKCS#12 file import in the LEGACY policy.

Impact by back-end:

  • OpenSSL: Translated into a Ciphersuites and CipherString list in opensslcnf.config. Removing AES-128-GCM disables ALL AES-128 ciphers (it is not possible to selectively disable an isolated mode).
  • GnuTLS: Cipher list in the priority string of gnutls.config.
  • NSS: Ciphersuite list in nss.config.
  • OpenSSH: Ciphers list in openssh.config and opensshserver.config.
  • Kerberos: Allowed encryption types (permitted_enctypes) in krb5.config.

mac — MAC Algorithms

Defines which Message Authentication Codes are allowed.

mac = HMAC-SHA2-256 HMAC-SHA2-384 HMAC-SHA2-512 \
      HMAC-SHA1 AEAD \
      UMAC-128 UMAC-64

Recognized values:

ValueDescription
HMAC-SHA2-256HMAC with SHA-256
HMAC-SHA2-384HMAC with SHA-384
HMAC-SHA2-512HMAC with SHA-512
HMAC-SHA1HMAC with SHA-1
AEADMACs integrated into AEAD ciphers (GCM, Poly1305)
UMAC-64UMAC with 64-bit tag (SSH)
UMAC-128UMAC with 128-bit tag (SSH)
HMAC-MD5HMAC with MD5 (insecure — legacy only)

Impact by back-end:

  • OpenSSH: Generates MACs list (hmac-sha2-256, hmac-sha2-512, umac-128-etm@openssh.com, etc.).
  • OpenSSL: Indirectly affected — CBC ciphers require HMAC-SHA1 and AES-256-CBC in the list to be enabled.
  • GnuTLS: HMAC-SHA2-256 and HMAC-SHA2-384 as standalone MACs are disabled due to concerns about constant-time implementation; only AEAD is used in practice for TLS 1.3.

hash — Hash Algorithms (Message Digest)

Defines which cryptographic hash functions are allowed for general use (different from signatures).

hash = SHA2-256 SHA2-384 SHA2-512 \
       SHA3-256 SHA3-384 SHA3-512 \
       SHA2-224 SHA1

Recognized values:

ValueOutput (bits)Common Use
SHA1160Legacy (insecure for signatures)
SHA2-224224Rare
SHA2-256256Modern standard
SHA2-384384TLS 1.3, certificates
SHA2-512512High security
SHA3-256256Alternative NIST standard
SHA3-384384Alternative NIST standard
SHA3-512512Alternative NIST standard
SHA3-224224SHA-3 with reduced output
SHAKE-128VariableExtendable-output function (XOF)
SHAKE-256VariableExtendable-output function (XOF)
MD5128Insecure — extreme legacy only

hash vs sign relationship: The hash parameter controls general use (HMAC, key derivation, DNSSec). To control which hashes are accepted in signatures, use sign.

sign — Signature Algorithms

Defines which algorithm+hash signature combinations are allowed.

sign = RSA-PSS-SHA2-256 RSA-PSS-SHA2-384 RSA-PSS-SHA2-512 \
       RSA-SHA2-256 RSA-SHA2-384 RSA-SHA2-512 \
       ECDSA-SHA2-256 ECDSA-SHA2-384 ECDSA-SHA2-512 \
       EDDSA-ED25519 EDDSA-ED448

Recognized values:

ValueDescription
RSA-SHA1RSA PKCS#1 v1.5 with SHA-1
RSA-SHA2-224RSA PKCS#1 v1.5 with SHA-224
RSA-SHA2-256RSA PKCS#1 v1.5 with SHA-256
RSA-SHA2-384RSA PKCS#1 v1.5 with SHA-384
RSA-SHA2-512RSA PKCS#1 v1.5 with SHA-512
RSA-PSS-SHA2-256RSA-PSS with SHA-256
RSA-PSS-SHA2-384RSA-PSS with SHA-384
RSA-PSS-SHA2-512RSA-PSS with SHA-512
ECDSA-SHA1ECDSA with SHA-1
ECDSA-SHA2-224ECDSA with SHA-224
ECDSA-SHA2-256ECDSA with SHA-256
ECDSA-SHA2-384ECDSA with SHA-384
ECDSA-SHA2-512ECDSA with SHA-512
EDDSA-ED25519EdDSA using Ed25519 curve
EDDSA-ED448EdDSA using Ed448 curve
DSA-SHA1DSA with SHA-1 (legacy)
DSA-SHA2-256DSA with SHA-256
ECDSA-SHA2-256-FIDOECDSA with SHA-256 via FIDO (WebAuthn)
EDDSA-ED25519-FIDOEdDSA Ed25519 via FIDO (WebAuthn)
RSA-PSS-RSAE-SHA2-256RSA-PSS (RSAE key) with SHA-256
RSA-PSS-RSAE-SHA2-384RSA-PSS (RSAE key) with SHA-384
RSA-PSS-RSAE-SHA2-512RSA-PSS (RSAE key) with SHA-512
RSA-SHA3-256RSA PKCS#1 v1.5 with SHA3-256
RSA-PSS-SHA3-256RSA-PSS with SHA3-256
ECDSA-SHA3-256ECDSA with SHA3-256
MLDSA44ML-DSA-44 (NIST post-quantum, level 2)
MLDSA65ML-DSA-65 (NIST post-quantum, level 3)
MLDSA87ML-DSA-87 (NIST post-quantum, level 5)
MLDSA65-ED25519ML-DSA-65 + Ed25519 hybrid (OpenPGP RFC 9980)
MLDSA87-ED448ML-DSA-87 + Ed448 hybrid (OpenPGP RFC 9980)
SLHDSA-SHAKE-128SSLH-DSA with SHAKE-128 small (OpenPGP RFC 9980)
SLHDSA-SHAKE-128FSLH-DSA with SHAKE-128 fast (OpenPGP RFC 9980)
SLHDSA-SHAKE-256SSLH-DSA with SHAKE-256 small (OpenPGP RFC 9980)

Practical impact: If an X.509 certificate was signed with RSA-SHA1 and the active policy does not include RSA-SHA1 in sign, the certificate chain of trust verification will fail. This is the most common cause of “certificate verify failed” after switching from LEGACY to DEFAULT.

PQ note: The ML-DSA algorithms are included by default in DEFAULT, FUTURE, and FIPS. The hybrid algorithms and SLH-DSA are included only for the sequoia and RPM (OpenPGP) scopes, per RFC 9980.

key_exchange — Key Exchange Methods

key_exchange = ECDHE DHE RSA PSK DHE-RSA DHE-DSS
ValueDescriptionForward Secrecy
ECDHEElliptic Curve Diffie-Hellman EphemeralYes
DHEDiffie-Hellman EphemeralYes
DHE-RSADHE authenticated with RSAYes
DHE-DSSDHE authenticated with DSS/DSAYes
RSAStatic RSA key exchangeNo
PSKPre-Shared KeyDepends
ECDHE-GSSECDHE with GSSAPI (SSH)Yes
DHE-GSSDHE with GSSAPI (SSH)Yes
KEM-ECDHKey Encapsulation Mechanism with ECDH (post-quantum)Yes
SNTRUPNTRU Prime with X25519 (SSH post-quantum)Yes
RSA-PSKPre-Shared Key with RSA authenticationNo
ECDHE-PSKECDHE with Pre-Shared KeyYes
DHE-PSKDHE with Pre-Shared KeyYes

Note: The FUTURE policy removes RSA and DHE-DSS from key exchange — this means that servers using certificates with DSA keys will not be able to establish connections.

Note on Libreswan: The key_exchange parameter does not affect the generated configuration for Libreswan. To limit DH/ECDH in IPsec, use the group parameter.

group — Groups/Curves for Key Exchange

Defines which elliptic curves and Diffie-Hellman groups are allowed.

group = MLKEM768-X25519 P256-MLKEM768 P384-MLKEM1024 MLKEM1024-X448 \
        X25519 X448 SECP256R1 SECP384R1 SECP521R1 \
        FFDHE-2048 FFDHE-3072 FFDHE-4096 FFDHE-6144 FFDHE-8192

Post-quantum groups (hybrid):

ValueTypeDescription
MLKEM768-X25519PQ HybridML-KEM-768 combined with X25519
P256-MLKEM768PQ HybridML-KEM-768 combined with NIST P-256
P384-MLKEM1024PQ HybridML-KEM-1024 combined with NIST P-384
MLKEM1024-X448PQ HybridML-KEM-1024 combined with X448

Classic groups:

ValueTypeSizeDescription
X25519Bernstein curve~128-bit sec.Modern, fast curve
X448Goldilocks curve~224-bit sec.Modern, high-security curve
SECP256R1NIST P-256256 bitsNIST standard curve
SECP384R1NIST P-384384 bitsNIST high-security curve
SECP521R1NIST P-521521 bitsNIST maximum-security curve
FFDHE-1024Finite Field DH1024 bitsLegacy DH group (LEGACY only)
FFDHE-1536Finite Field DH1536 bitsDH group (LEGACY only)
FFDHE-2048Finite Field DH2048 bitsStandardized DH group (RFC 7919)
FFDHE-3072Finite Field DH3072 bitsStandardized DH group
FFDHE-4096Finite Field DH4096 bitsStandardized DH group
FFDHE-6144Finite Field DH6144 bitsStandardized DH group
FFDHE-8192Finite Field DH8192 bitsStandardized DH group

Note on OpenSSL: The order of group values is only respected within PQ (post-quantum) and classic “classes.” All PQ groups are automatically ordered above classic ones. In opensslcnf.config, the format is Groups = *pq_group1:pq_group2/*classic_group1:classic_group2 where * indicates group classes and / separates classes.

Note on NSS: The order of group values is ignored — NSS uses its internal built-in order.

Note on FIPS and PQ: In the FIPS policy, ML-KEM is supported for TLS groups but disabled by scope for OpenSSL (not supported in the FIPS provider), Sequoia/RPM, and partially OpenSSH.

protocol — Allowed Protocol Versions

protocol = TLS1.3 TLS1.2 DTLS1.2 IKEv2
ValueDescription
SSL3.0SSLv3 (removed from libraries — cannot be enabled)
TLS1.0TLS 1.0
TLS1.1TLS 1.1
TLS1.2TLS 1.2
TLS1.3TLS 1.3
DTLS0.9DTLS 0.9
DTLS1.0DTLS 1.0
DTLS1.2DTLS 1.2
IKEv1IKE version 1 (legacy IPsec)
IKEv2IKE version 2 (modern IPsec)

Limitation: Some back-ends (OpenSSL, NSS) do not allow selectively disabling protocol versions — they use the oldest version in the list as the lower bound. Disabling all TLS and/or DTLS versions results in the library defaults being applied.

I.5.2 Integer Parameters

ParameterDescriptionExampleImpact
min_rsa_sizeMinimum RSA key size in bits2048Connections with smaller RSA keys are rejected. Affects ALL back-ends.
min_dh_sizeMinimum DH parameter size in bits2048DH key exchange with smaller parameters is rejected.
min_dsa_sizeMinimum DSA key size in bits2048Smaller DSA keys are rejected.
min_ec_sizeMinimum EC key size in bits256Applies only to the Java/OpenJDK back-end.

How OpenSSL applies minimum sizes: OpenSSL does not have fine granularity for minimum sizes — it uses the @SECLEVEL mechanism that defines security ranges. min_rsa_size = 2048 corresponds to @SECLEVEL=2. min_rsa_size = 3072 corresponds to @SECLEVEL=3. This means that not all arbitrary values are possible.

SECLEVELRSA minDH minECC minHash minSecurity
0000-Everything allowed
110241024160SHA-180 bits
220482048224SHA-224112 bits
330723072256SHA-256128 bits
476807680384SHA-384192 bits
51536015360512SHA-512256 bits

I.5.3 Boolean Parameters (0 or 1)

ParameterDescriptionDefault (DEFAULT)Impact
sha1_in_certsAllows SHA-1 in certificate signatures0Applies only to the GnuTLS back-end. If 0, certificates signed with SHA-1 are rejected during chain validation.
arbitrary_dh_groupsAllows arbitrary (non-standardized) DH groups0If 0, only standardized FFDHE groups (RFC 7919) are accepted. If 1, accepts arbitrary DH parameters generated by the server.
ssh_certsAllows OpenSSH certificate authentication1If 0, disables the OpenSSH certificate mechanism (not to be confused with X.509 certificates).

I.5.4 Enumeration Parameters

ParameterValuesDescription
etmANY, DISABLE_ETM, DISABLE_NON_ETMControls Encrypt-then-MAC vs Encrypt-and-MAC. Implemented only for SSH. Use with scope @SSH. ANY allows both. DISABLE_ETM forces Encrypt-and-MAC (E&M). DISABLE_NON_ETM forces Encrypt-then-MAC (EtM).
__emsDEFAULT, ENFORCE, RELAXInternal. Controls Extended Master Secret (RFC 7627). ENFORCE is used by the FIPS policy to force EMS. RELAX disables the requirement (NO-ENFORCE-EMS subpolicy). Affects OpenSSL and GnuTLS. See section I.14 for details.

I.5.5 Deprecated Parameters

The following parameters still work but should be migrated. The source code (preprocess_text in cryptopolicies.py) performs the conversion automatically and emits a FutureWarning:

DeprecatedExact Automatic ConversionReason
min_tls_version = TLS1.2protocol@TLS = -SSL2.0 -SSL3.0 -TLS1.0 -TLS1.1Replaced by incremental removal
min_tls_version = TLS1.3protocol@TLS = -SSL2.0 -SSL3.0 -TLS1.0 -TLS1.1 -TLS1.2Same
min_dtls_version = DTLS1.2protocol@TLS = -DTLS0.9 -DTLS1.0Same
ike_protocol = IKEv2protocol@IKE = IKEv2Renamed to scope
tls_cipher = ...cipher@TLS = ...Renamed to scope
ssh_cipher = ...cipher@SSH = ...Renamed to scope
ssh_group = ...group@SSH = ...Renamed to scope
sha1_in_dnssec = 0hash@DNSSec = -SHA1 + sign@DNSSec = -RSA-SHA1 -ECDSA-SHA1Split into two parameters
sha1_in_dnssec = 1hash@DNSSec = SHA1+ + sign@DNSSec = RSA-SHA1+ ECDSA-SHA1+Same
ssh_etm = 0etm@SSH = DISABLE_ETMRenamed to enum
ssh_etm = 1etm@SSH = ANYSame
ssh_etm@<scope> = 0etm@<scope> = DISABLE_ETMSupports scope

Additionally, the value X25519-MLKEM768 is automatically converted to MLKEM768-X25519 (algorithm rename, RHEL-99813).

The protocol parameter (without scope) still works but emits a warning — it should be replaced by protocol@TLS.


I.6 Difference Between .pol and .pmod

.pol File — Full Policy

  • Defines all parameters from scratch.
  • Used as the base policy.
  • Applied with update-crypto-policies --set MYPOLICY.
  • Must contain values for all relevant options (otherwise, they remain empty/library defaults).
  • Does not evolve with crypto-policies package updates — if Red Hat adds new back-ends or parameters, your custom policy will not include them automatically.

.pmod File — Subpolicy/Modifier Module

  • Selectively modifies an existing base policy.
  • Does not need to define all parameters — only the ones it wants to change.
  • Applied with update-crypto-policies --set BASE:MYPOLICY.
  • Evolves with updates — since it only modifies the base, when the base is updated by the package, the modifications remain and apply over the new version.
  • Multiple modules can be stacked: --set DEFAULT:MOD1:MOD2:MOD3.

Red Hat strongly recommends using subpolicies (.pmod) instead of custom full policies (.pol). This ensures that security updates to base policies propagate automatically.

Concatenation Mechanism

When the effective policy is DEFAULT:NO-SHA1:MY-MODULE, the system concatenates the files in this order:

DEFAULT.pol + NO-SHA1.pmod + MY-MODULE.pmod

Each subsequent directive overrides or modifies the previous one. If DEFAULT.pol defines:

hash = SHA2-256 SHA2-384 SHA2-512 SHA1

And NO-SHA1.pmod defines:

hash = -SHA1
sign = -RSA-SHA1 -RSA-PSS-SHA1 -ECDSA-SHA1

The resulting effective policy will have hash without SHA1 and sign without any SHA-1-based algorithm.


I.7 Creating Custom Subpolicies — Step-by-Step Guide

Example 1: Require Minimum 4096-bit RSA Keys

sudo tee /etc/crypto-policies/policies/modules/RSA-4096.pmod << 'EOF'
min_rsa_size = 4096
min_dh_size = 4096
EOF

sudo update-crypto-policies --set DEFAULT:RSA-4096

Effect: Any certificate with an RSA key smaller than 4096 bits will be rejected. Any DH key exchange with parameters smaller than 4096 bits will be rejected. This is extremely restrictive — most commercial certificates use 2048 or 4096 bits.

Example 2: Policy for TLS 1.3-Only Environment

sudo tee /etc/crypto-policies/policies/modules/TLS13-ONLY.pmod << 'EOF'
protocol@TLS = TLS1.3
protocol@!TLS = -TLS1.0 -TLS1.1
cipher@TLS = AES-256-GCM AES-128-GCM CHACHA20-POLY1305
key_exchange = ECDHE
EOF

sudo update-crypto-policies --set DEFAULT:TLS13-ONLY

Effect: Only TLS 1.3 is allowed for TLS connections. Only AEAD ciphers (the only ones TLS 1.3 supports). Key exchange via ECDHE only.

Example 3: Active Directory Compatibility

sudo tee /etc/crypto-policies/policies/modules/AD-COMPAT.pmod << 'EOF'
cipher@Kerberos = +AES-128-CBC +AES-256-CBC +RC4-128
hash = SHA1 SHA2-256 SHA2-384 SHA2-512+
group = +FFDHE-1024
min_dh_size = 1024
sha1_in_certs = 1
EOF

sudo update-crypto-policies --set DEFAULT:AD-COMPAT

Effect: Enables ciphers needed for Kerberos with legacy AD, allows SHA-1 in certificates (needed for old DCs), accepts 1024-bit DH parameters.

Example 4: Maximum Security for Financial Environment

sudo tee /etc/crypto-policies/policies/modules/FINANCE-SEC.pmod << 'EOF'
protocol@TLS = TLS1.3 TLS1.2
cipher = AES-256-GCM CHACHA20-POLY1305
cipher@SSH = AES-256-GCM AES-256-CTR CHACHA20-POLY1305
mac = HMAC-SHA2-256 HMAC-SHA2-384 HMAC-SHA2-512 AEAD
hash = SHA2-256 SHA2-384 SHA2-512
sign = RSA-PSS-SHA2-256 RSA-PSS-SHA2-384 RSA-PSS-SHA2-512 \
       ECDSA-SHA2-256 ECDSA-SHA2-384 ECDSA-SHA2-512 \
       EDDSA-ED25519 EDDSA-ED448
key_exchange = ECDHE DHE
group = X25519 X448 SECP384R1 SECP521R1 FFDHE-4096 FFDHE-8192
min_rsa_size = 4096
min_dh_size = 4096
sha1_in_certs = 0
arbitrary_dh_groups = 0
ssh_certs = 1
etm@SSH = DISABLE_NON_ETM
EOF

sudo update-crypto-policies --set FUTURE:FINANCE-SEC

Example 5: Module to Disable CBC in Everything

sudo tee /etc/crypto-policies/policies/modules/NO-CBC.pmod << 'EOF'
cipher = -AES-256-CBC -AES-128-CBC -CAMELLIA-256-CBC -CAMELLIA-128-CBC
EOF

sudo update-crypto-policies --set DEFAULT:NO-CBC

I.8 Creating a Full Policy (.pol) from Scratch

For scenarios where no base policy fits and subpolicies are not sufficient:

sudo cp /usr/share/crypto-policies/policies/DEFAULT.pol \
        /etc/crypto-policies/policies/MYORG.pol

sudo vi /etc/crypto-policies/policies/MYORG.pol

Full policy example:

# /etc/crypto-policies/policies/MYORG.pol
# Custom organizational policy

# Allowed protocols
protocol = TLS1.3 TLS1.2 DTLS1.2 IKEv2

# Symmetric ciphers
cipher = AES-256-GCM AES-128-GCM CHACHA20-POLY1305
cipher@Kerberos = AES-256-CBC AES-128-CBC+

# MACs
mac = HMAC-SHA2-256 HMAC-SHA2-384 HMAC-SHA2-512 AEAD UMAC-128
mac@SSH = HMAC-SHA2-256 HMAC-SHA2-512 AEAD UMAC-128

# Hashes
hash = SHA2-256 SHA2-384 SHA2-512

# Signatures
sign = ECDSA-SHA2-256 ECDSA-SHA2-384 ECDSA-SHA2-512 \
       RSA-PSS-SHA2-256 RSA-PSS-SHA2-384 RSA-PSS-SHA2-512 \
       RSA-SHA2-256 RSA-SHA2-384 RSA-SHA2-512 \
       EDDSA-ED25519 EDDSA-ED448

# Key exchange
key_exchange = ECDHE DHE

# Groups/Curves
group = X25519 X448 SECP256R1 SECP384R1 SECP521R1 \
        FFDHE-3072 FFDHE-4096 FFDHE-6144 FFDHE-8192

# Minimum key sizes
min_rsa_size = 3072
min_dh_size = 3072
min_dsa_size = 3072

# Boolean options
sha1_in_certs = 0
arbitrary_dh_groups = 0
ssh_certs = 1

Apply:

sudo update-crypto-policies --set MYORG

Warning: With a custom .pol policy, future updates to the crypto-policies package that improve the DEFAULT policy will not propagate to MYORG. You are responsible for keeping your policy up to date.


I.9 The local.d Mechanism — Per-Back-end Overrides

The /etc/crypto-policies/local.d/ directory allows adding extra configuration that is concatenated to the end of the generated back-end file. This does NOT modify the policy — it directly modifies the library’s configuration file.

# Example: Add extra configuration to OpenSSL
sudo tee /etc/crypto-policies/local.d/opensslcnf-extra.config << 'EOF'
# Extra configuration concatenated to opensslcnf.config
# (OpenSSL configuration format, not policy format)
EOF

Naming convention: The file must follow the pattern <backend>-<suffix>.config, where <backend> corresponds to the back-end name (without the .config extension).

Recommended use: Extreme situations where the policy does not offer the necessary control over a specific back-end. This is a last-resort mechanism — prefer subpolicies.


I.10 How Each Back-end Consumes the Policy

OpenSSL (OpenSSLGenerator + OpenSSLFIPSGenerator)

The generator creates two files: opensslcnf.config (general configuration) and openssl_fips.config (FIPS module configuration). OpenSSL reads /etc/crypto-policies/back-ends/opensslcnf.config during initialization via the .include directive in the main openssl.cnf file. The applied scopes are {'tls', 'ssl', 'openssl'}.

Parameter translation:

Policy ParameterOpenSSL Configuration
cipherCiphersuites (TLS 1.3) and CipherString (TLS 1.2)
min_rsa_size, min_dh_size@SECLEVEL=N at the beginning of CipherString
protocolTLS.MinProtocol, TLS.MaxProtocol, DTLS.MinProtocol, DTLS.MaxProtocol
groupGroups (PQ and classic separated by /)
signSignatureAlgorithms (with ? prefix for tolerance of unknown algorithms)
__emsOptions = RHNoEnforceEMSinFIPS if RELAX
__openssl_block_sha1_signaturesrh-allow-sha1-signatures = yes/no
min_rsa_sizedefault_bits in the [req] section (minimum 2048)

OpenSSL quirks:

  1. The TLS 1.2 cipher list (CipherString) is generated by subtraction — it starts by listing the enabled key_exchange, then removes the ciphers, key exchanges, and MACs from the disabled list. This differs from the allowlist model used by GnuTLS.
  2. The CipherString always excludes -SHA384, -CAMELLIA, -ARIA, -AESCCM8, and -CBC (when all CBC are disabled) through hardcoding in the generator.
  3. To disable CCM ciphers, both AES-128-CCM and AES-256-CCM need to be removed; the generator uses the keyword -AESCCM.
  4. All SignatureAlgorithms are prefixed with ? (?RSA+SHA256) so that OpenSSL tolerates algorithms it does not know (e.g.: post-quantum in older versions).

GnuTLS (GnuTLSGenerator)

The generator creates a configuration file in allowlist mode (no longer priority strings as in older versions). The applied scopes are {'tls', 'ssl', 'gnutls'}. The configuration is read from /etc/crypto-policies/back-ends/gnutls.config via the GNUTLS_SYSTEM_PRIORITY_FILE variable (or the compiled-in default path).

Translation:

Policy ParameterGnuTLS Configuration
ciphertls-enabled-cipher = AES-256-GCM, etc.
mactls-enabled-mac = AEAD, tls-enabled-mac = SHA512
hashsecure-hash = SHA256, etc.
signsecure-sig = RSA-SHA256 + secure-sig-for-cert = RSA-SHA256
sha1_in_certsAdditional secure-sig-for-cert = rsa-sha1/dsa-sha1/ecdsa-sha1
grouptls-enabled-group = GROUP-X25519 + enabled-curve = X25519
key_exchangetls-enabled-kx = ECDHE-RSA, tls-enabled-kx = ECDHE-ECDSA
protocolenabled-version = TLS1.3, etc.
min_rsa_size, min_dh_sizemin-verification-profile
__emstls-session-hash = require/request

GnuTLS quirks:

  1. sha1_in_certs is the only back-end that respects this parameter directly (adding SHA-1 only to secure-sig-for-cert).
  2. HMAC-SHA2-256 and HMAC-SHA2-384 as standalone MACs are mapped to None in the code and therefore never enabled, due to Lucky13 vulnerability concerns (see GnuTLS issue #503). Only AEAD and HMAC-SHA2-512 work.
  3. PSK key exchanges (PSK, DHE-PSK, ECDHE-PSK, RSA-PSK) are commented out in the generator and therefore never enabled via crypto-policies, even if the policy lists them.
  4. The ECDHE key exchange is expanded into two kex types: ECDHE-RSA and ECDHE-ECDSA in the generator.
  5. Curves need to be enabled separately from groups. The generator extracts curves from groups and signatures (e.g.: EdDSA-Ed25519 → enabled-curve = Ed25519).

NSS (Network Security Services)

The generator creates an NSS policy file at /etc/crypto-policies/back-ends/nss.config.

NSS quirks:

  1. The order of group is ignored — NSS uses its internal order.
  2. It is the only back-end that respects scopes pkcs12, pkcs12-import, smime, and smime-import.
  3. pkcs12 implies pkcs12-import — it is not possible to allow export without allowing import.
  4. These scopes cannot enable signature algorithms that were not enabled in the general configuration.
  5. Disabling all TLS/DTLS versions results in the library defaults.

OpenSSH (OpenSSHClientGenerator + OpenSSHServerGenerator)

The generator creates two separate files with different scopes:

  • Client: openssh.config — scopes {'ssh', 'openssh', 'openssh-client'}
  • Server: opensshserver.config — scopes {'ssh', 'openssh', 'openssh-server'}

Translation:

Policy ParameterOpenSSH Configuration
cipher@SSHCiphers
mac@SSH + etmMACs (EtM and non-EtM in order according to the etm enum)
key_exchange × group × hashKexAlgorithms (Cartesian product filtered by the kx_map table)
key_exchange × hash + arbitrary_dh_groupsKexAlgorithms via gx_map (group exchange)
signPubkeyAcceptedAlgorithms, HostbasedAcceptedAlgorithms, CASignatureAlgorithms
sign (server)HostKeyAlgorithms (server only)
ssh_certs + sign-cert-v01@openssh.com suffixes added to PubkeyAcceptedAlgorithms
min_rsa_sizeRequiredRSASize (if > 0)
key_exchange (GSS)GSSAPIKexAlgorithms or GSSAPIKeyExchange no

OpenSSH quirks:

  1. DH group 1 (1024 bits) is always removed on the server, even if the policy allows 1024-bit DH. The server code explicitly does del local_kx_map[('DHE', 'FFDHE-1024', 'SHA1')].
  2. HostKeyAlgorithms is defined only for the server. Setting it on the client would break handling of existing known_hosts entries.
  3. KexAlgorithms is built by a Cartesian product of (key_exchange, group, hash), filtered against the kx_map table. Only combinations with a defined mapping produce output.
  4. GSSAPIKeyExchange no is emitted if no GSS kex is enabled by the policy.
  5. CASignatureAlgorithms does not include certificate variants, only base algorithms.
  6. When ssh_certs = 1, the generator adds certificates including the new ssh-mldsa44-ed25519@openssh.com (post-quantum).
  7. The server reload command is systemctl try-restart sshd.service (restart, not reload, because systemd needs to re-read command-line options).

Libreswan (IPsec/IKE)

Libreswan quirks:

  1. The key_exchange parameter does not affect the generated configuration for Libreswan.
  2. To control DH vs ECDH in IPsec, use the group parameter.

Kerberos (MIT krb5)

The generator creates /etc/crypto-policies/back-ends/krb5.config with the permitted_enctypes list.

Translation: Ciphers are mapped to Kerberos enctypes (aes256-cts-hmac-sha384-192, aes128-cts-hmac-sha256-128, etc.).

Java/OpenJDK

The generator creates /etc/crypto-policies/back-ends/java.config which configures Java Security Properties.

Note: The min_ec_size parameter applies only to the Java back-end.


I.11 Removed vs Disabled Algorithms

There is a fundamental distinction between algorithms removed from libraries and algorithms disabled by policies:

Completely Removed from Core Libraries

These algorithms have been removed from the source code of cryptographic libraries and cannot be enabled by any policy, not even LEGACY:

Algorithm/ProtocolReason
DES (not 3DES)Completely insecure (56 bits)
Export-grade cipher suitesInsecure by design
MD5 in signaturesDemonstrated collisions
SSLv2Multiple fatal vulnerabilities
SSLv3Vulnerable to POODLE
ECC curves < 224 bitsInsecure
Binary field ECC curvesNon-standardized / suspect

Disabled in ALL Pre-defined Policies (but available)

These algorithms exist in the libraries but are disabled in all policies. A custom .pol policy could enable them (not recommended):

Algorithm/ProtocolRisk
DH with parameters < 1024 bitsLogjam attack
RSA with key < 1024 bitsFactorable with modern hardware
CamelliaNot widely tested
RC4Multiple known biases
ARIALimited use, little auditing
SEEDLimited use outside Korea
IDEAObsolete
Integrity-only ciphersuitesNo encryption
TLS CBC with HMAC SHA-384Problematic implementations
AES-CCM8 (short tag)Insufficient authentication tag
ECC curves incompatible with TLS 1.3 (including secp256k1)Outside the TLS 1.3 standard
IKEv1Replaced by IKEv2

I.12 Applications and Libraries NOT Covered

Crypto-policies cover only data in transit (data-in-transit). The following situations are not controlled:

Not CoveredReason
Go applicationsThe Go runtime does not read system crypto-policies
GnuPG-2Uses its own configuration system
Data at rest (disk encryption)LUKS, dm-crypt have independent configuration
Certificate locationsManaged by each service individually
CA trust storeManaged by update-ca-trust, not by crypto-policies
Certificate issuanceNot controlled (CA/certmonger/certbot)
Applications that force their own configurationsIf the app defines ciphers explicitly in code, the system policy is ignored

I.13 Scope Hierarchy — Source Code View

The source code (cryptopolicies.py) defines a scope hierarchy where each back-end receives a set of scopes and optionally inherits from a parent scope. The following table shows exactly which scopes are applied for each back-end when generating its configuration:

Back-end (dumpable)Parent scopeSet of applied scopes
bind—bind, dnssec
gnutls—gnutls, tls, ssl
java-tls—java-tls, tls, ssl
krb5—krb5, kerberos
libreswan—ipsec, ike, libreswan
libssh—libssh, ssh
nss—nss
nss-tlsnssnss, nss-tls, tls, ssl
nss-pkcs12nssnss, pkcs12, nss-pkcs12
nss-pkcs12-importnss-pkcs12nss, pkcs12, pkcs12-import, nss-pkcs12, nss-pkcs12-import
nss-smimenssnss, smime, nss-smime
nss-smime-importnss-smimenss, smime, smime-import, nss-smime, nss-smime-import
openssh—openssh, ssh
openssh-clientopensshopenssh-client, openssh, ssh
openssh-serveropensshopenssh-server, openssh, ssh
openssl—openssl, tls, ssl
sequoia—sequoia
rpm—rpm, rpm-sequoia

How it works: When the OpenSSH server generator requests the configuration, it passes the set {'ssh', 'openssh', 'openssh-server'} to the ScopedPolicy. Each directive in the policy is evaluated against these scopes: cipher@SSH matches (because ssh is in the set), cipher@TLS does not match, cipher@!SSH does not match. The directive cipher@{SSH,TLS} would match because ssh is in the set.

Parent-child inheritance: When the policy dump is generated for CURRENT.pol, the system shows scope-specific properties only if they differ from the parent scope. For example, cipher@openssh-server only appears if it differs from cipher@openssh.


I.14 Internal Parameters (Not Publicly Documented)

The source code defines parameters with a __ (double underscore) prefix that are used internally by the system-provided policies. These parameters are not documented in the man page and should not be used in custom policies, but understanding them is important for comprehending the actual behavior:

__openssl_block_sha1_signatures

INT_DEFAULTS = {
    ...
    '__openssl_block_sha1_signatures': 1,
}

Controls whether OpenSSL blocks SHA-1 signature verification. Default value 1 (block). In the OpenSSL generator, this is translated to the rh-allow-sha1-signatures = yes/no directive in the [evp_properties] section of opensslcnf.config. This is a Red Hat-specific extension to OpenSSL, not present in upstream OpenSSL.

The LEGACY policy defines __openssl_block_sha1_signatures = 0, allowing SHA-1 signature verification in OpenSSL. All other policies maintain the value 1.

__ems (Extended Master Secret)

ENUMS = {
    'etm': ('ANY', 'DISABLE_ETM', 'DISABLE_NON_ETM'),
    '__ems': ('DEFAULT', 'ENFORCE', 'RELAX'),
}

Controls the Extended Master Secret (RFC 7627) requirement in TLS. Three possible values:

ValueEffect on OpenSSLEffect on GnuTLS
DEFAULTNo action (library decides)No action (tls-session-hash not configured)
ENFORCENo action (FIPS already forces it)tls-session-hash = require
RELAXOptions = RHNoEnforceEMSinFIPS + FIPS config with tls1-prf-ems-check = 0tls-session-hash = request

The FIPS policy defines __ems = ENFORCE. The NO-ENFORCE-EMS subpolicy defines __ems = RELAX.

The OpenSSL generator also creates a separate file openssl_fips.config that configures the OpenSSL FIPS module:

[fips_sect]
tls1-prf-ems-check = 1  # or 0 if __ems == RELAX
activate = 1

I.15 Post-Quantum Algorithms in the Source Code

The source code (alg_lists.py) already defines extensive lists of post-quantum algorithms, marked as experimental:

Groups (Key Encapsulation — ML-KEM)

The following post-quantum groups are defined in the current policies (DEFAULT, FUTURE, FIPS, LEGACY):

GroupTypeDescription
MLKEM768-X25519HybridML-KEM-768 with X25519 (NIST standard + classic)
P256-MLKEM768HybridML-KEM-768 with NIST P-256
P384-MLKEM1024HybridML-KEM-1024 with NIST P-384
MLKEM1024-X448HybridML-KEM-1024 with X448

Groups marked as experimental (present in the code but not in default policies):

GroupStatus
MLKEM512, X25519-MLKEM512, P256-MLKEM512Experimental
MLKEM768, X448-MLKEM768Experimental (solo/alternative variants)
MLKEM1024, P521-MLKEM1024Experimental

Signatures (ML-DSA, FALCON, SPHINCS+, SLH-DSA)

Post-quantum signatures in default policies:

AlgorithmPresent in DEFAULTPresent in FIPS
MLDSA44 (ML-DSA-44)YesYes
MLDSA65 (ML-DSA-65)YesYes
MLDSA87 (ML-DSA-87)YesYes

Experimental signatures in the code (not in default policies):

P256-MLDSA44, RSA3072-MLDSA44, MLDSA44-PSS2048, MLDSA44-RSA2048,
MLDSA44-ED25519, MLDSA44-P256, MLDSA44-BP256,
P384-MLDSA65, MLDSA65-PSS3072, MLDSA65-RSA3072,
FALCON512, FALCONPADDED512, FALCON1024, FALCONPADDED1024,
SPHINCSSHA2128FSIMPLE, SPHINCSSHA2128SSIMPLE, SPHINCSSHAKE128FSIMPLE,
... and more hybrid variants

PQ signatures for OpenPGP (Sequoia/RPM), added with sign@{sequoia,RPM} in all default policies:

MLDSA65-ED25519, MLDSA87-ED448,
SLHDSA-SHAKE-128S, SLHDSA-SHAKE-128F, SLHDSA-SHAKE-256S

How OpenSSL handles PQ groups: The OpenSSL generator separates groups into two classes — PQ and classic — and formats them as *pq_groups/classic_groups in the Groups directive. This causes servers to prefer any PQ group over any classic group when both are supported, and clients to send key shares for the highest-priority PQ group AND the highest-priority classic group.


I.16 FIPS Auto-Bind-Mount — The Boot Mechanism

When the kernel is started with fips=1, a dracut module and/or a systemd service automatically bind-mount:

/usr/share/crypto-policies/back-ends/FIPS/  →  /etc/crypto-policies/back-ends/
/usr/share/crypto-policies/default-fips-config  →  /etc/crypto-policies/config

This ensures that the FIPS policy is active from the very first moment of boot, even before update-crypto-policies can be executed.

The update-crypto-policies.py script detects this situation by checking /proc/self/mountinfo:

is_fips_auto_bind_mounted():
  Checks if /etc/crypto-policies/config is mounted from
  .../crypto-policies/default-fips-config
  AND if /etc/crypto-policies/back-ends is mounted from
  .../crypto-policies/back-ends/FIPS

Behavior when auto-bind is active:

  • --show: Works normally (reads the mounted content).
  • --set FIPS:SUBPOLICY: Unmounts the bind-mounts with umount and then applies the new policy with the subpolicy. This allows customizing FIPS with subpolicies.
  • --set DEFAULT (or other non-FIPS): Issues a warning that the system will no longer be FIPS-compliant, unmounts and applies.
  • Without --set: Warns that files in local.d/ will be ignored while auto-bind is active.

Practical implication: If you need FIPS with a subpolicy (e.g.: FIPS:NO-ENFORCE-EMS), run update-crypto-policies --set FIPS:NO-ENFORCE-EMS — this removes the automatic bind-mount and applies a persistent custom FIPS policy.


I.17 What Each Policy Actually Defines — Source Code Annotations

The following annotations are extracted directly from the .pol files of the upstream repository.

DEFAULT.pol — Annotations

# 112-bit security with SHA-1 exception in DNSSec

# Includes ML-KEM and ML-DSA (post-quantum) at the top of the lists
group = MLKEM768-X25519 P256-MLKEM768 P384-MLKEM1024 MLKEM1024-X448 \
        X25519 SECP256R1 X448 SECP521R1 SECP384R1 \
        FFDHE-2048 FFDHE-3072 FFDHE-4096 FFDHE-6144 FFDHE-8192

# SHA-1 and DSA-SHA1 allowed only in DNSSec and RPM (by scope)
hash@DNSSec = SHA1+
sign@DNSSec = RSA-SHA1+ ECDSA-SHA1+
sign@RPM = DSA-SHA1+
hash@RPM = SHA1+
min_dsa_size@RPM = 1024   # RPM needs to accept old packages with DSA 1024

# CBC disabled in SSH by scope (vulnerable to plaintext recovery)
cipher@SSH = -*-CBC

# RSA is BEFORE DHE in key_exchange for interoperability reasons
key_exchange = KEM-ECDH ECDHE RSA DHE DHE-RSA PSK DHE-PSK ECDHE-PSK RSA-PSK \
               ECDHE-GSS DHE-GSS

# arbitrary_dh_groups = 1 → accepts DH parameters from the server
# (necessary for compatibility with servers that don't use FFDHE)
arbitrary_dh_groups = 1

FUTURE.pol — Differences from DEFAULT

# 128-bit security — post-quantum preparation

# HMAC-SHA1 REMOVED from the MAC list
mac = AEAD HMAC-SHA2-256 UMAC-128 HMAC-SHA2-384 HMAC-SHA2-512
# (DEFAULT includes HMAC-SHA1)

# SHA2-224 and SHA3-224 REMOVED from hash
hash = SHA2-256 SHA2-384 SHA2-512 SHA3-256 SHA3-384 SHA3-512 SHAKE-256

# No SHA-1 in ANY scope (no exception for DNSSec)
# SHA-224 REMOVED from sign
# RSA REMOVED from key_exchange (no static RSA key exchange)
key_exchange = KEM-ECDH ECDHE DHE DHE-RSA PSK DHE-PSK ECDHE-PSK ECDHE-GSS DHE-GSS

# Only 256-bit ciphers and AEAD in TLS
cipher@TLS = AES-256-GCM AES-256-CCM CHACHA20-POLY1305

# FFDHE-2048 REMOVED (minimum 3072)
group = ... FFDHE-3072 FFDHE-4096 FFDHE-6144 FFDHE-8192

# Minimum sizes increased
min_dh_size = 3072
min_dsa_size = 3072
min_rsa_size = 3072

LEGACY.pol — Differences from DEFAULT

# 64-bit security — maximum compatibility

# SHA1 included in hash (for general use, not just DNSSec)
hash = ... SHA1

# DSA and SHA-1 ALLOWED in signatures
sign = ... DSA-SHA2-256 DSA-SHA2-384 DSA-SHA2-512 DSA-SHA2-224 \
       ECDSA-SHA1 RSA-PSS-SHA1 RSA-SHA1 DSA-SHA1

# 3DES-CBC enabled in cipher and cipher@TLS
cipher = ... 3DES-CBC
cipher@TLS = ... 3DES-CBC

# CBC ENABLED in SSH (different from DEFAULT/FUTURE/FIPS)
cipher@SSH = AES-256-GCM CHACHA20-POLY1305 AES-256-CTR AES-256-CBC \
    AES-128-GCM AES-128-CTR AES-128-CBC 3DES-CBC

# DHE-DSS enabled, FFDHE-1536 included, FFDHE-1024 for SSH
group@SSH = FFDHE-1024+
key_exchange = ... DHE-DSS ...

# TLS 1.0 and 1.1 enabled, DTLS 1.0 enabled
protocol@TLS = TLS1.3 TLS1.2 TLS1.1 TLS1.0 DTLS1.2 DTLS1.0

# Minimum sizes reduced
min_dh_size = 1024
min_dsa_size = 1024
min_rsa_size = 1024

# SHA-1 allowed in certificates (GnuTLS)
sha1_in_certs = 1

# OpenSSL does NOT block SHA-1 signatures
__openssl_block_sha1_signatures = 0

# PKCS#12 accepts DES, RC4, RC2, SEED (extreme legacy)
cipher@pkcs12 = AES-256-CBC AES-192-CBC AES-128-CBC \
    CAMELLIA-256-CBC ... 3DES-CBC DES-CBC RC4-128 DES40-CBC RC2-CBC SEED-CBC

FIPS.pol — Differences from DEFAULT

# FIPS 140 compliance — does NOT guarantee FIPS by itself

# No CHACHA20-POLY1305 (not FIPS-approved)
cipher@TLS = AES-256-GCM AES-256-CCM AES-256-CBC \
    AES-128-GCM AES-128-CCM AES-128-CBC

# No X25519 and X448 in groups (not FIPS-approved as standalone curves)
# ML-KEM blocked for OpenSSL (not supported in the FIPS provider yet)
# ML-KEM blocked for Sequoia/RPM
group@openssl = +P256-MLKEM768  # deprioritize X25519-MLKEM768
group@{sequoia,rpm} = -MLKEM768-X25519 -P256-MLKEM768 -P384-MLKEM1024
group@openssh = -MLKEM768-X25519

# No EdDSA, no FIDO, no SHA-224 in signatures
# No RSA-PSS-RSAE SHA3 variants

# PSK without RSA-PSK (not approved)
key_exchange = KEM-ECDH ECDHE DHE DHE-RSA PSK DHE-PSK ECDHE-PSK

# Extended Master Secret mandatory
__ems = ENFORCE

I.18 Algorithm Mapping in Generators — Source Code Tables

OpenSSH: Cipher Mapping

The openssh.py generator maps generic cipher names to the names used by OpenSSH:

PolicyOpenSSH
AES-256-GCMaes256-gcm@openssh.com
AES-256-CTRaes256-ctr
AES-128-GCMaes128-gcm@openssh.com
AES-128-CTRaes128-ctr
CHACHA20-POLY1305chacha20-poly1305@openssh.com
AES-256-CBCaes256-cbc
AES-128-CBCaes128-cbc
3DES-CBC3des-cbc
AES-256-CCM(not supported)
CAMELLIA-*(not supported)

Ciphers without a mapping in OpenSSH are silently ignored.

OpenSSH: Key Exchange Mapping

The generator builds the KexAlgorithms list from combinations (key_exchange, group, hash):

Combination (key_exchange, group, hash)SSH KexAlgorithm
ECDHE, X25519, SHA2-256curve25519-sha256, curve25519-sha256@libssh.org
ECDHE, SECP256R1, SHA2-256ecdh-sha2-nistp256
ECDHE, SECP384R1, SHA2-384ecdh-sha2-nistp384
ECDHE, SECP521R1, SHA2-512ecdh-sha2-nistp521
DHE, FFDHE-2048, SHA2-256diffie-hellman-group14-sha256
DHE, FFDHE-4096, SHA2-512diffie-hellman-group16-sha512
DHE, FFDHE-8192, SHA2-512diffie-hellman-group18-sha512
KEM-ECDH, MLKEM768-X25519, SHA2-256mlkem768x25519-sha256
KEM-ECDH, P256-MLKEM768, SHA2-256mlkem768nistp256-sha256
KEM-ECDH, P384-MLKEM1024, SHA2-384mlkem1024nistp384-sha384

The server always removes ('DHE', 'FFDHE-1024', 'SHA1') (diffie-hellman-group1-sha1), even if the policy allows it.

OpenSSL: SECLEVEL Mechanism

The OpenSSL generator (openssl.py) determines the @SECLEVEL directly from the min_dh_size and min_rsa_size parameters:

if min_dh_size < 1023 or min_rsa_size < 1023:
    s = '@SECLEVEL=0'
elif min_dh_size < 2048 or min_rsa_size < 2048:
    s = '@SECLEVEL=1'
elif min_dh_size < 3072 or min_rsa_size < 3072:
    s = '@SECLEVEL=2'
else:
    s = '@SECLEVEL=3'

This means that if min_rsa_size = 2048 and min_dh_size = 2048, OpenSSL will receive @SECLEVEL=2. There is no way to define an intermediate value (e.g.: RSA 2048 but DH 3072 would result in SECLEVEL=2 because of the RSA).

OpenSSL: TLS 1.3 Cipher Generation

The generator builds the Ciphersuites directive (TLS 1.3) separately from the CipherString (TLS 1.2). For TLS 1.3, there is a direct mapping:

CiphersuiteRequires cipherRequires hash
TLS_AES_256_GCM_SHA384AES-256-GCMSHA2-384
TLS_AES_128_GCM_SHA256AES-128-GCMSHA2-256
TLS_CHACHA20_POLY1305_SHA256CHACHA20-POLY1305SHA2-256
TLS_AES_128_CCM_SHA256AES-128-CCMSHA2-256

OpenSSL: SHA-1 and the rh-allow-sha1-signatures Section

The generator always adds a special section to opensslcnf.config:

[openssl_init]
alg_section = evp_properties

[evp_properties]
rh-allow-sha1-signatures = no   # or yes for LEGACY

This is a Red Hat extension that controls whether OpenSSL accepts verifying SHA-1-based signatures. The logic in the code is:

sha1_sig = not policy.integers['__openssl_block_sha1_signatures']
s += RH_SHA1_SECTION.format('yes' if sha1_sig else 'no')

Only the LEGACY policy defines __openssl_block_sha1_signatures = 0.

GnuTLS: Allowlist Mode

The GnuTLS generator (gnutls.py) generates configuration in allowlist mode:

[global]
override-mode = allowlist

[overrides]
secure-hash = SHA256
secure-hash = SHA384
...
tls-enabled-mac = AEAD
tls-enabled-mac = SHA512
...
tls-enabled-group = GROUP-X25519
tls-enabled-group = GROUP-SECP256R1
...
secure-sig = RSA-SHA256
secure-sig = ECDSA-SHA256
...
secure-sig-for-cert = RSA-SHA256
...
enabled-curve = X25519
enabled-curve = SECP256R1
...
tls-enabled-cipher = AES-256-GCM
...
tls-enabled-kx = ECDHE-RSA
tls-enabled-kx = ECDHE-ECDSA
...
enabled-version = TLS1.3
enabled-version = TLS1.2
...
min-verification-profile = medium   # or high, ultra, etc.

[priorities]
SYSTEM=NONE

The min-verification-profile is derived from minimum key sizes:

min_dh_size / min_rsa_sizeProfile
≤ 768very_weak
≤ 1024low
≤ 2048medium
≤ 3072high
≤ 8192ultra
> 8192future

When sha1_in_certs = 1, the generator explicitly adds:

secure-sig-for-cert = rsa-sha1
secure-sig-for-cert = dsa-sha1
secure-sig-for-cert = ecdsa-sha1

GnuTLS: MACs Disabled for Security

The GnuTLS mac_map maps HMAC-SHA2-256 and HMAC-SHA2-384 to None:

mac_map = {
    'HMAC-SHA2-256': None,  # not allowlisted over concerns that
    'HMAC-SHA2-384': None,  # implementation might be vulnerable to Lucky13
    'HMAC-SHA2-512': 'SHA512',
}

This means that even if the policy enables HMAC-SHA2-256, GnuTLS will not activate it as a standalone MAC. Only HMAC-SHA2-512 and AEAD work as TLS MACs in GnuTLS. The reference in the code points to GnuTLS issue #503.


I.19 Generator Validation — Automatic Configuration Tests

Each generator contains a test_config() method that validates the generated configuration by executing the library’s actual binary. This happens during update-crypto-policies:

GeneratorTest CommandWhat It Validates
OpenSSLGeneratoropenssl ciphers <CipherString>Verifies that the CipherString is valid and does not contain ADH
GnuTLSGeneratorgnutls-cli -l (with GNUTLS_SYSTEM_PRIORITY_FILE pointing to the config)Verifies that the priority string is valid
OpenSSHClientGeneratorssh -G -F <config> bogus_serverVerifies that the SSH options are valid
OpenSSHServerGeneratorsshd -T -h <hostkey> -f <config>Generates a temporary 3072-bit RSA host key and tests sshd

If the library binary is not installed, the test is silently skipped. Environment variables OLD_OPENSSH=1 and OLD_GNUTLS=1 also skip the tests (for compatibility with old versions during builds).


The update-crypto-policies.py script uses atomic writing to prevent corruption:

  1. Creates a temporary file with mkstemp() in the destination directory.
  2. Writes the content, performs fsync(), sets permissions 0o644.
  3. Does os.rename() from temporary to final name (atomic operation on the same filesystem).

Symlink optimization: If the policy has no subpolicies AND no files exist in local.d/, the script creates a symlink from /etc/crypto-policies/back-ends/<backend>.config to /usr/share/crypto-policies/<POLICY>/<backend>.txt instead of copying the content. This saves space and allows package updates to propagate automatically for simple policies (without modules).

If local.d/ files exist, the symlink cannot be used because the content needs to be concatenated. In that case, the file is written directly and the content of local.d/ is appended.


I.21 Effective Policy Verification and Diagnosis

Verification Commands

# Active policy (name)
update-crypto-policies --show

# Verify if the policy is actually applied
# (compares timestamps and content of state/current vs config,
#  and checks if there is no active FIPS auto-bind-mount)
update-crypto-policies --is-applied

# Verify if the generated files match the configured policy
# (regenerates in a temporary directory and compares byte by byte)
update-crypto-policies --check

# Expanded effective policy (result of base + submodule concatenation)
cat /etc/crypto-policies/state/CURRENT.pol

# Content of each back-end's config file
cat /etc/crypto-policies/back-ends/opensslcnf.config
cat /etc/crypto-policies/back-ends/gnutls.config
cat /etc/crypto-policies/back-ends/nss.config
cat /etc/crypto-policies/back-ends/openssh.config
cat /etc/crypto-policies/back-ends/opensshserver.config

# Date of last policy change
ls -l /etc/crypto-policies/back-ends/
ls -l /etc/crypto-policies/config

# Available ciphers in OpenSSL under the current policy
openssl ciphers -v

# Test a real TLS connection and verify negotiated cipher
openssl s_client -connect localhost:443

# Check if any service is overriding the policy
grep -r "SSLProtocol\|SSLCipherSuite" /etc/httpd/ 2>/dev/null
grep -r "ssl_protocols\|ssl_ciphers" /etc/nginx/ 2>/dev/null

Validating a Subpolicy Before Applying

# Check syntax — update-crypto-policies will report errors
sudo update-crypto-policies --set DEFAULT:MY-NEW-MODULE 2>&1

# If there is a syntax error, the output will indicate the problem

# Compare back-ends before/after
diff <(cat /etc/crypto-policies/back-ends/opensslcnf.config) \
     <(sudo update-crypto-policies --set DEFAULT:MY-NEW-MODULE && \
       cat /etc/crypto-policies/back-ends/opensslcnf.config)

# Verify the expanded effective policy
cat /etc/crypto-policies/state/CURRENT.pol

I.22 Differences Between RHEL Versions

AspectRHEL 8RHEL 9RHEL 10
OpenSSL1.1.13.0.x / 3.2.x3.x
.pmod subpoliciesIntroduced in RHEL 8.2Fully supportedFully supported
@scope scopesBasic (RHEL 8.5+)CompleteComplete
Wildcards *RHEL 8.2+YesYes
Scope negation @!scopeLimitedYesYes
BSI policyNoYes (Fedora/RHEL 9+)Yes
Post-quantum (ML-KEM, ML-DSA)NoPartial (late)Yes
DEFAULT blocks SHA-1In signatures (except DNSSec)More aggressiveMore aggressive
FIPS policyFIPS 140-2FIPS 140-3FIPS 140-3
NEXT as alias for DEFAULTNoYesYes
Sequoia/RPM back-endsNoYesYes

I.23 System-Provided Subpolicies

The following subpolicies come installed with the crypto-policies package:

NO-SHA1

hash = -SHA1
sign = -RSA-PSS-SHA1 -RSA-SHA1 -ECDSA-SHA1 -EDDSA-ED25519

Removes SHA-1 from hashes and signatures. Total SHA-1 blocking system-wide.

AD-SUPPORT

Enables algorithms needed for interoperability with Active Directory and legacy Windows environments.

NO-CAMELLIA

cipher = -CAMELLIA-*

Removes all Camellia ciphers.

NO-ENFORCE-EMS (RHEL 9+)

Disables the Extended Master Secret (EMS) requirement in TLS. Needed for compatibility with old TLS libraries that do not support RFC 7627.

GOST

Enables GOST cryptographic algorithms (Russian standard GOST R 34.10-2012, GOST R 34.11-2012). Needed for compliance with Russian Federation cryptography standards.


I.24 Practical Diagnostic Examples

“certificate verify failed” After Policy Change

Probable cause:
  - Certificate signed with SHA-1 → sign does not include RSA-SHA1/ECDSA-SHA1
  - Certificate with 1024-bit RSA key → min_rsa_size rejects it
  - Intermediate CA with SHA-1 → sha1_in_certs = 0 (GnuTLS)

Diagnosis:
  openssl x509 -in cert.pem -noout -text | grep "Signature Algorithm"
  openssl x509 -in cert.pem -noout -text | grep "Public-Key"
  update-crypto-policies --show
  cat /etc/crypto-policies/state/CURRENT.pol | grep -E "sign|min_rsa|sha1"

Fix:
  If legacy certificate → reissue with SHA-256 and RSA 2048+
  If temporary → create subpolicy allowing the needed algorithm

SSH Refuses Connection with “no matching cipher found”

Probable cause:
  - Server or client offers only CBC ciphers and the policy disabled them
  - Old client only supports AES-128-CBC

Diagnosis:
  ssh -vvv user@host 2>&1 | grep -i cipher
  cat /etc/crypto-policies/back-ends/openssh.config
  cat /etc/crypto-policies/back-ends/opensshserver.config

Fix:
  Create subpolicy that re-enables the needed cipher:
  cipher@SSH = +AES-128-CBC

IPsec/VPN Fails to Negotiate

Probable cause:
  - DH group of the other endpoint is not in the 'group' list
  - IKEv1 protocol blocked

Diagnosis:
  cat /etc/crypto-policies/back-ends/libreswan.config
  journalctl -u ipsec | grep -i "no proposal"

Fix:
  group = +FFDHE-1024   # If the other endpoint uses DH 1024
  protocol@IKE = IKEv1 IKEv2+  # If the other endpoint uses IKEv1

I.25 Summary Matrix: Parameter × Back-end

ParameterOpenSSLGnuTLSNSSOpenSSHlibsshKerberosLibreswanBINDJava
cipherYesYesYesYesYesYesYes-Yes
macPartialPartial-YesYes----
hashYesYesYes---YesYesYes
signYesYesYesYesYes-YesYesYes
key_exchangeYesYesYesYesYes-No-Yes
groupYesYesYesYesYes-Yes-Yes
protocolYesYesYes---Yes-Yes
min_rsa_sizeSECLEVELProfileYes-----Yes
min_dh_sizeSECLEVELProfileYes-----Yes
min_dsa_sizeSECLEVELProfileYes-----Yes
min_ec_size--------Yes
sha1_in_certs-Yes-------
arbitrary_dh_groupsYesYes-------
ssh_certs---Yes-----
etm---YesYes----
__emsYesYes-------
__openssl_block_sha1_signaturesYes--------

Legend: Yes = fully implemented, Partial = indirect or limited implementation, No = explicitly does not affect, - = not applicable.

Note: Parameters with the __ prefix are internal and not publicly documented. See section I.14 for details.


I.26 Architecture Recommendations

When to Use Each Approach

NeedApproachReason
Fine-tuning over existing policy.pmod subpolicy over DEFAULTEvolves with updates
Total controlCustom .pol policyNo automatic evolution — admin’s responsibility
Override for one application onlylocal.d/ or explicit configuration in the appDoes not affect the rest of the system
Temporary compatibilityLEGACY for a limited timeSecurity risk — document and plan exit
Regulatory complianceFIPS + subpolicy if neededAutomated by fips-mode-setup

Decision Flow for Custom Policy

Do I need to change cryptographic settings?
    │
    ├─ Only a specific application?
    │   └─ Yes → Configure directly in the app OR use local.d/
    │
    ├─ Do I want to disable something system-wide?
    │   └─ Yes → Create .pmod with '-' directives
    │           Apply with DEFAULT:MY-MODULE
    │
    ├─ Do I want to enable something that DEFAULT blocks?
    │   └─ Yes → Create .pmod with '+' directives
    │           Apply with DEFAULT:MY-MODULE
    │           ⚠️ Document why it is needed
    │
    ├─ Do I want absolute control of everything?
    │   └─ Yes → Create a full .pol (copy from DEFAULT.pol)
    │           ⚠️ Responsible for ongoing maintenance
    │
    └─ None of the above?
        └─ Use DEFAULT and don't touch it

I.27 File and Command Reference

Essential Commands

# View active policy
update-crypto-policies --show

# Change policy
sudo update-crypto-policies --set <POLICY>[:MODULE1][:MODULE2]

# List available base policies
ls /usr/share/crypto-policies/policies/*.pol

# List available subpolicies
ls /usr/share/crypto-policies/policies/modules/*.pmod

# List local subpolicies
ls /etc/crypto-policies/policies/modules/*.pmod 2>/dev/null

# View effective policy (expanded)
cat /etc/crypto-policies/state/CURRENT.pol

# View a specific library's back-end
cat /etc/crypto-policies/back-ends/<backend>.config

# Check if it changed recently
stat /etc/crypto-policies/config

Directory Summary

DirectoryPurposeWho Manages
/usr/share/crypto-policies/policies/RPM package base policiescrypto-policies package
/usr/share/crypto-policies/policies/modules/RPM package subpoliciescrypto-policies package
/etc/crypto-policies/policies/Local custom policiesAdministrator
/etc/crypto-policies/policies/modules/Local custom subpoliciesAdministrator
/etc/crypto-policies/back-ends/Generated configs for each libraryupdate-crypto-policies
/etc/crypto-policies/local.d/Per-back-end extra overridesAdministrator
/etc/crypto-policies/state/Current state (symbolic link, expanded policy)update-crypto-policies
/etc/crypto-policies/configText name of the active policyupdate-crypto-policies

I.28 Sources and References

Documentation

  • Official man page: man 7 crypto-policies — primary and canonical source for all parameters, scopes, and behaviors documented in this appendix.
  • Man page: man 8 update-crypto-policies — command usage.
  • On-disk policy documentation: /usr/share/doc/crypto-policies/
  • Red Hat Developer: Enhance security with system-wide crypto policies in RHEL 9
  • FOSDEM 2020: Custom crypto policies — presentation by the original maintainer, Tomas Mraz.
  • Red Hat Knowledge Base: Article 3642912 — crypto-policies reference.
  • RFC 7457: Summarizing Known Attacks on Transport Layer Security (TLS) — motivation for algorithm deprecation.
  • RFC 7627: Transport Layer Security (TLS) Session Hash and Extended Master Secret Extension.
  • RFC 9980: Post-Quantum Public Key Algorithm Extension for the OpenPGP Standard (ML-DSA, SLH-DSA).

Source Code (Upstream Repository)

License

The fedora-crypto-policies project is distributed under the LGPL-2.1-or-later license. Copyright © 2019 Red Hat, Inc. — Tomas Mraz and contributors.