Skip to content

Support encrypted PEM TLS certificate key - #51567

Open
michalvavrik wants to merge 1 commit into
keycloak:mainfrom
michalvavrik:feature/issues/27437
Open

Support encrypted PEM TLS certificate key#51567
michalvavrik wants to merge 1 commit into
keycloak:mainfrom
michalvavrik:feature/issues/27437

Conversation

@michalvavrik

Copy link
Copy Markdown
Member

@michalvavrik
michalvavrik requested a review from a team as a code owner August 10, 2026 00:08
Copilot AI balanced review requested due to automatic review settings August 10, 2026 00:08
@michalvavrik
michalvavrik requested a review from a team as a code owner August 10, 2026 00:08
@michalvavrik michalvavrik changed the title feat(quarkus): support encrypted PME TLS certificate key Support encrypted PEM TLS certificate key Aug 10, 2026
michalvavrik added a commit to michalvavrik/keycloak that referenced this pull request Aug 10, 2026
closes: keycloak#27437

Signed-off-by: Michal Vavřík <dev@michalvavrik.net>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-type configuration 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.

Comment thread docs/guides/server/enabletls.adoc Outdated
Copilot AI review requested due to automatic review settings August 10, 2026 00:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: validateStoreType rejects an unrecognized extension, and HttpDistTest.httpStoreTypeValidation expects not-there.ks to fail. Document that https-key-store-type is 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.

michalvavrik added a commit to michalvavrik/keycloak that referenced this pull request Aug 10, 2026
closes: keycloak#27437

Signed-off-by: Michal Vavřík <dev@michalvavrik.net>
Copilot AI review requested due to automatic review settings August 10, 2026 00:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 -pass so 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.

michalvavrik added a commit to michalvavrik/keycloak that referenced this pull request Aug 10, 2026
closes: keycloak#27437

Signed-off-by: Michal Vavřík <dev@michalvavrik.net>
Copilot AI review requested due to automatic review settings August 10, 2026 00:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.ks fails 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. dispatchStoreFile only 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 -pass so OpenSSL prompts securely, or demonstrate a non-command-line password source.
openssl genpkey -algorithm RSA -out keyfile.pem -aes-256-cbc -pass pass:<password>

michalvavrik added a commit to michalvavrik/keycloak that referenced this pull request Aug 10, 2026
closes: keycloak#27437

Signed-off-by: Michal Vavřík <dev@michalvavrik.net>
Copilot AI review requested due to automatic review settings August 10, 2026 10:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.ks prevents 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. Mention https-management-key-store-file as 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;

michalvavrik added a commit to michalvavrik/keycloak that referenced this pull request Aug 10, 2026
closes: keycloak#27437

Signed-off-by: Michal Vavřík <dev@michalvavrik.net>
Copilot AI review requested due to automatic review settings August 10, 2026 12:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-file source in addition to the canonical mapper at line 251. PropertyMappers.java:330-350 treats 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>
Copilot AI review requested due to automatic review settings August 10, 2026 12:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 dispatchStoreFile treats PEM as present and returns null. 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);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Accept encrypted PEM TLS certificate key

2 participants