Support encrypted PEM TLS certificate key - #51567
Conversation
michalvavrik
commented
Aug 10, 2026
- closes: Accept encrypted PEM TLS certificate key #27437
closes: keycloak#27437 Signed-off-by: Michal Vavřík <dev@michalvavrik.net>
5c3e19d to
2861e23
Compare
There was a problem hiding this comment.
Pull request overview
Adds encrypted PKCS#8 PEM key support by migrating HTTPS configuration to Quarkus TLS Registry.
Changes:
- Adds HTTP and management PEM key-password options.
- Migrates TLS mappings, reload, SNI, and store dispatch behavior.
- Adds integration/unit tests and documentation.
Reviewed changes
Copilot reviewed 21 out of 23 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
quarkus/tests/integration/src/test/resources/unencrypted-test.key.pem |
Adds an unencrypted test key. |
quarkus/tests/integration/src/test/resources/encrypted-test.key.pem |
Adds an encrypted test key. |
quarkus/tests/integration/src/test/resources/encrypted-test.crt.pem |
Adds its matching certificate. |
quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/HttpDistTest.java |
Tests encrypted PEM and TLS reload. |
quarkus/tests/integration/src/test/java/org/keycloak/it/cli/dist/FipsDistTest.java |
Updates TLS error assertions. |
quarkus/tests/integration/src/test-providers/java/org/keycloak/it/resource/realm/TestRealmResource.java |
Adds a TLS reload test endpoint. |
quarkus/runtime/src/test/resources/conf/keycloak.conf |
Updates the management TLS property. |
quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/mappers/TlsStoreDispatchTest.java |
Tests TLS store dispatch. |
quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappersTlsExceptionTest.java |
Removes obsolete exception-mapping tests. |
quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/ManagementConfigurationTest.java |
Tests management TLS mappings. |
quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/configuration/ConfigurationTest.java |
Tests HTTP TLS Registry mappings. |
quarkus/runtime/src/test/java/org/keycloak/quarkus/runtime/cli/PicocliTest.java |
Tests TLS validation and precedence. |
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/services/KeycloakHttpServerOptionsCustomizer.java |
Enables SNI on HTTPS options. |
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakRecorder.java |
Uses centralized SNI detection. |
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/KeycloakMain.java |
Removes transformer reset logic. |
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ManagementPropertyMappers.java |
Migrates management TLS mappings. |
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java |
Implements TLS Registry mapping and validation. |
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/cli/ExecutionExceptionHandler.java |
Removes TLS exception transformers. |
quarkus/config-api/src/main/java/org/keycloak/config/ManagementOptions.java |
Adds the management PEM password option. |
quarkus/config-api/src/main/java/org/keycloak/config/HttpOptions.java |
Adds the HTTP PEM password option. |
docs/guides/server/enabletls.adoc |
Documents encrypted PEM configuration. |
docs/documentation/upgrading/topics/changes/changes-26_8_0.adoc |
Adds an upgrade note. |
docs/documentation/release_notes/topics/26_8_0.adoc |
Adds a release note. |
Suppressed comments (1)
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ManagementPropertyMappers.java:224
- The same missing management-side validation affects trust stores: an unrecognized management truststore extension is routed as PKCS12 instead of producing the
https-management-trust-store-typeconfiguration error. Validate the management truststore type before this dispatch.
.mapFrom(ManagementOptions.HTTPS_MANAGEMENT_TRUST_STORE_FILE, (name, fileValue, context) ->
HttpPropertyMappers.dispatchStoreFile(fileValue, ManagementOptions.HTTPS_MANAGEMENT_TRUST_STORE_TYPE, type, HttpPropertyMappers.StoreRole.TRUST_STORE))
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 23 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
docs/guides/server/enabletls.adoc:56
- This conflicts with the implemented and tested behavior:
validateStoreTyperejects an unrecognized extension, andHttpDistTest.httpStoreTypeValidationexpectsnot-there.ksto fail. Document thathttps-key-store-typeis required for unrecognized extensions (or change the implementation and tests to use the stated PKCS12 fallback).
If your keystore does not have one of these extensions, it is treated as PKCS12. To override this, set the `https-key-store-type` option.
closes: keycloak#27437 Signed-off-by: Michal Vavřík <dev@michalvavrik.net>
2861e23 to
c3db56e
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 24 changed files in this pull request and generated no new comments.
Suppressed comments (6)
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java:316
- The password dispatch has the same HTTP-only PEM check even when called by
ManagementPropertyMappers. A management PEM configuration can therefore retain the inherited/explicit key-store password; base this check on the management certificate option when dispatching the management store.
if (role == StoreRole.KEY_STORE && getOptionalKcValue(HttpOptions.HTTPS_CERTIFICATE_FILE.getKey()).isPresent()) {
return null;
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java:304
- This helper is also used for management key stores, but it only checks the HTTP PEM option. When management PEM files and a management (or inherited) key store are both configured, the key-store path is still emitted, defeating the intended PEM precedence and potentially configuring two key-store types; select the corresponding management certificate option for management dispatches.
This issue also appears on line 315 of the same file.
if (role == StoreRole.KEY_STORE && getOptionalKcValue(HttpOptions.HTTPS_CERTIFICATE_FILE.getKey()).isPresent()) {
return null;
quarkus/config-api/src/main/java/org/keycloak/config/ManagementOptions.java:123
- The new management password option is only covered by configuration-mapping tests; the added end-to-end encrypted-PEM test exercises only the main HTTPS listener. Add a management-listener integration test with the encrypted fixture so the separate management TLS bucket's decryption and password inheritance/override behavior is verified.
public static final Option<String> HTTPS_MANAGEMENT_CERTIFICATE_KEY_FILE_PASSWORD = new OptionBuilder<>("https-management-certificate-key-file-password", String.class)
.category(OptionCategory.MANAGEMENT)
.description("The password to decrypt an encrypted private key in PEM format for the management server. If not given, the value is inherited from HTTP options. " + RELEVANT_MSG)
.build();
docs/guides/server/enabletls.adoc:30
- This example puts the encryption password in the process arguments, where it may be visible to other users, directly contradicting the security guidance below. Omit
-passso OpenSSL prompts securely, or demonstrate a non-command-line password source.
openssl genpkey -algorithm RSA -out keyfile.pem -aes-256-cbc -pass pass:<password>
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java:383
- Key-store type validation runs before PEM precedence is applied. Consequently, a valid PEM configuration combined with an intentionally ignored key store having an unrecognized extension still fails startup; skip key-store validation whenever the corresponding effective PEM certificate option is present, for both listeners.
validateStoreType(HttpOptions.HTTPS_KEY_STORE_FILE, HttpOptions.HTTPS_KEY_STORE_TYPE, StoreRole.KEY_STORE);
validateStoreType(HttpOptions.HTTPS_TRUST_STORE_FILE, HttpOptions.HTTPS_TRUST_STORE_TYPE, StoreRole.TRUST_STORE);
validateStoreType(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_FILE, ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_TYPE, StoreRole.KEY_STORE);
validateStoreType(ManagementOptions.HTTPS_MANAGEMENT_TRUST_STORE_FILE, ManagementOptions.HTTPS_MANAGEMENT_TRUST_STORE_TYPE, StoreRole.TRUST_STORE);
docs/documentation/upgrading/topics/changes/changes-26_8_0.adoc:58
- “Unencrypted PEM keys continue to work without any changes” conflicts with the new
validateNoFipsPem()behavior, which now rejects every PEM certificate in both strict and non-strict FIPS modes. Either preserve the prior non-strict behavior or document this breaking restriction and its migration path here.
A new `--https-certificate-key-file-password` option (env: `KC_HTTPS_CERTIFICATE_KEY_FILE_PASSWORD`) has been added to support encrypted PKCS#8 private keys in PEM format. If your PEM private key is encrypted, provide the decryption password using this option. The management interface has a corresponding `--https-management-certificate-key-file-password` option. Unencrypted PEM keys continue to work without any changes.
closes: keycloak#27437 Signed-off-by: Michal Vavřík <dev@michalvavrik.net>
c3db56e to
1d5a83d
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 24 changed files in this pull request and generated no new comments.
Suppressed comments (3)
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java:380
- PEM is meant to take precedence over a simultaneously configured keystore, but this unconditional validation still rejects an ignored keystore with an unknown extension. For example, a valid PEM pair plus
--https-key-store-file=server.ksfails before dispatch; skip HTTP keystore type validation when the PEM certificate option is present.
validateStoreType(HttpOptions.HTTPS_KEY_STORE_FILE, HttpOptions.HTTPS_KEY_STORE_TYPE, StoreRole.KEY_STORE);
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ManagementPropertyMappers.java:160
- When HTTP uses a keystore but management supplies its own PEM pair, the effective management keystore still inherits the HTTP file.
dispatchStoreFileonly checks the HTTP PEM option, so this emits both P12/JKS and PEM paths into the management TLS bucket; suppress the management keystore whenever the effective management PEM certificate is present.
.mapFrom(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_FILE, (name, fileValue, context) ->
HttpPropertyMappers.dispatchStoreFile(fileValue, ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_TYPE, type, HttpPropertyMappers.StoreRole.KEY_STORE))
docs/guides/server/enabletls.adoc:30
- This example exposes the private-key passphrase in the OpenSSL process arguments, contradicting the security guidance below. Omit
-passso OpenSSL prompts securely, or demonstrate a non-command-line password source.
openssl genpkey -algorithm RSA -out keyfile.pem -aes-256-cbc -pass pass:<password>
closes: keycloak#27437 Signed-off-by: Michal Vavřík <dev@michalvavrik.net>
1d5a83d to
682287d
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 34 changed files in this pull request and generated no new comments.
Suppressed comments (3)
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java:384
- This rejects an unrecognized keystore even when a PEM pair is configured and the dispatch logic intentionally gives PEM precedence. Skip HTTP keystore type validation when the PEM certificate option is present, otherwise a stale
--https-key-store-file=server.ksprevents an otherwise valid PEM configuration from starting.
validateStoreType(HttpOptions.HTTPS_KEY_STORE_FILE, HttpOptions.HTTPS_KEY_STORE_TYPE, StoreRole.KEY_STORE);
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java:418
- For a management-only PEM configuration this error directs the user to the main server's
https-key-store-file, which does not replace the rejected management key material. Mentionhttps-management-key-store-fileas well so the remediation is valid for the branch added here.
if (getOptionalKcValue(HttpOptions.HTTPS_CERTIFICATE_FILE.getKey()).isPresent()
|| getOptionalKcValue(ManagementOptions.HTTPS_MANAGEMENT_CERTIFICATE_FILE.getKey()).isPresent()) {
throw new PropertyException(
"PEM certificates are not supported in FIPS mode. Use a BCFKS keystore with the 'https-key-store-file' option instead.");
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java:303
- These HTTP-specific precedence checks are also used by the management dispatchers. When the main interface uses a keystore and management overrides it with a PEM pair, the inherited management keystore is not suppressed, while a main PEM pair incorrectly suppresses an explicit management keystore; pass the relevant certificate option into both dispatch helpers so each interface resolves its own key material.
This issue also appears in the following locations of the same file:
- line 384
- line 415
// PEM takes precedence over keystore when both are configured
if (role == StoreRole.KEY_STORE && getOptionalKcValue(HttpOptions.HTTPS_CERTIFICATE_FILE.getKey()).isPresent()) {
return null;
closes: keycloak#27437 Signed-off-by: Michal Vavřík <dev@michalvavrik.net>
682287d to
b3fbd04
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 34 changed files in this pull request and generated 2 comments.
Suppressed comments (2)
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java:257
- This loop creates three non-synthetic mappers with the same
kc.https-trust-store-filesource in addition to the canonical mapper at line 251.PropertyMappers.java:330-350treats this as a duplicate-mapper error during every command startup; dispatch targets need unique synthetic options mapped from the one public trust-store option.
fromOption(HttpOptions.HTTPS_TRUST_STORE_FILE)
.mapFrom(HttpOptions.HTTPS_TRUST_STORE_FILE, (name, fileValue, context) ->
dispatchStoreFile(fileValue, HttpOptions.HTTPS_TRUST_STORE_TYPE, type, StoreRole.TRUST_STORE))
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ManagementPropertyMappers.java:226
- These management trust-store dispatch entries all reuse the public option as a non-synthetic mapper, duplicating the canonical mapper at lines 213-218. Command sanitization rejects them under
PropertyMappers.java:330-350, so use unique synthetic target options that map from the canonical management trust-store option.
fromOption(ManagementOptions.HTTPS_MANAGEMENT_TRUST_STORE_FILE)
.isEnabled(ManagementPropertyMappers::isInheritedScheme, HTTP_MANAGEMENT_SCHEME_IS_INHERITED)
.mapFrom(ManagementOptions.HTTPS_MANAGEMENT_TRUST_STORE_FILE, (name, fileValue, context) ->
HttpPropertyMappers.dispatchStoreFile(fileValue, ManagementOptions.HTTPS_MANAGEMENT_TRUST_STORE_TYPE, type, HttpPropertyMappers.StoreRole.TRUST_STORE))
closes: keycloak#27437 Signed-off-by: Michal Vavřík <dev@michalvavrik.net>
b3fbd04 to
d31bb60
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 34 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/HttpPropertyMappers.java:413
- The new TLS validations run for every command, while only the missing-TLS check is scoped to serving commands. As a result,
build,show-config, import, or export can now fail on runtime-only TLS environment settings (for example, an unrecognized keystore extension or missing truststore password) even though those options are ignored; return early for non-serving commands before running these checks.
validateNoFipsPem();
quarkus/runtime/src/main/java/org/keycloak/quarkus/runtime/configuration/mappers/ManagementPropertyMappers.java:161
- An explicitly configured management keystore is silently suppressed when the main interface uses PEM: the management certificate option resolves to the inherited HTTP certificate, so
dispatchStoreFiletreats PEM as present and returnsnull. Explicit management TLS material should override inherited HTTP material; only apply PEM precedence when management PEM was explicitly set or when the management keystore itself is inherited.
.mapFrom(ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_FILE, (name, fileValue, context) ->
HttpPropertyMappers.dispatchStoreFile(fileValue, ManagementOptions.HTTPS_MANAGEMENT_KEY_STORE_TYPE, type,
HttpPropertyMappers.StoreRole.KEY_STORE, ManagementOptions.HTTPS_MANAGEMENT_CERTIFICATE_FILE))
| throw new PropertyException(Messages.httpsConfigurationNotSet()); | ||
| } | ||
| } | ||
| validateStoreType(HttpOptions.HTTPS_KEY_STORE_FILE, HttpOptions.HTTPS_KEY_STORE_TYPE, StoreRole.KEY_STORE); |