Skip to content

[autobackport: sssd-2-13] oidc_child: add JWT and mTLS authentication#8774

Merged
alexey-tikhonov merged 7 commits into
SSSD:sssd-2-13from
sssd-bot:SSSD-sssd-backport-pr8708-to-sssd-2-13
Jun 8, 2026
Merged

[autobackport: sssd-2-13] oidc_child: add JWT and mTLS authentication#8774
alexey-tikhonov merged 7 commits into
SSSD:sssd-2-13from
sssd-bot:SSSD-sssd-backport-pr8708-to-sssd-2-13

Conversation

@sssd-bot

@sssd-bot sssd-bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

This is an automatic backport of PR#8708 oidc_child: add JWT and mTLS authentication to branch sssd-2-13, created by @sumit-bose.

Please make sure this backport is correct.

Note

The commits were cherry-picked without conflicts.

You can push changes to this pull request

git remote add sssd-bot [email protected]:sssd-bot/sssd.git
git fetch sssd-bot refs/heads/SSSD-sssd-backport-pr8708-to-sssd-2-13
git checkout SSSD-sssd-backport-pr8708-to-sssd-2-13
git push sssd-bot SSSD-sssd-backport-pr8708-to-sssd-2-13 --force

Original commits
8100381 - crypto: add get_jwk_from_pkcs12()
ec078ea - oidc_child: add pkcs12-client-creds option
6577434 - oidc_child: add JWT authentication
9a3487d - test: add tests for oidc_child 'get-device-code'

Backported commits

  • 20f67e8 - crypto: add get_jwk_from_pkcs12()
  • 66d7407 - oidc_child: add pkcs12-client-creds option
  • 669b014 - oidc_child: add JWT authentication
  • 64a5e86 - test: add tests for oidc_child 'get-device-code'

Original Pull Request Body

With the new option --client-auth-method oidc_child can select between
authentication with a client secret, mutual-TLS/mTLS (RFC-8705) and JWT
client assertion (RFC-7523). The latter require a PKCS#12 file with the
client credentials (certificate and private key) and the password to
unlock the private key must be provided with the --client-secret or
--client-secret-stdin option.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for client authentication methods (MTLS and JWT client assertions) in oidc_child by extracting JWK from PKCS#12 files using OpenSSL's libcrypto. It updates the CLI options, HTTP request handling, and adds corresponding unit and system tests. The review highlights several critical issues: potential NULL pointer dereferences after PKCS12_parse and before open(), a double-free vulnerability when json_pack fails, and compilation breakage on platforms with OpenSSL 1.1.1 due to the unconditional use of OpenSSL 3.0+ APIs.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +574 to +579
ret = PKCS12_parse(pkcs12, password, &pkey, &cert, NULL);
if (ret != 1) {
DEBUG(SSSDBG_OP_FAILURE, "Failed to extract private key.\n");
ret = EIO;
goto done;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-critical critical

PKCS12_parse can return 1 (success) even if the PKCS#12 file does not contain a private key or certificate. In such cases, pkey or cert will be set to NULL. Unconditionally dereferencing pkey in EVP_PKEY_base_id(pkey) or passing cert to get_cert_sha256_hash will cause a segmentation fault. A NULL check must be added for both pointers after parsing.

    ret = PKCS12_parse(pkcs12, password, &pkey, &cert, NULL);
    if (ret != 1) {
        DEBUG(SSSDBG_OP_FAILURE, "Failed to extract private key.\n");
        ret = EIO;
        goto done;
    }

    if (pkey == NULL || cert == NULL) {
        DEBUG(SSSDBG_OP_FAILURE, "PKCS#12 file is missing private key or certificate.\n");
        ret = EINVAL;
        goto done;
    }

Comment on lines +1235 to +1240
json = json_pack("{s:o}", "payload", payload_b64);
if (json == NULL) {
json_decref(payload_b64);
DEBUG(SSSDBG_OP_FAILURE, "Failed to generate JSON payload.\n");
return NULL;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-critical critical

Jansson's json_pack with the o format specifier steals the reference of the passed json_t * (payload_b64) even if the packing fails. Therefore, calling json_decref(payload_b64) in the error block results in a double-decref (double free) / use-after-free vulnerability. The json_decref call should be removed from the error block.

    json = json_pack("{s:o}", "payload", payload_b64);
    if (json == NULL) {
        DEBUG(SSSDBG_OP_FAILURE, "Failed to generate JSON payload.\n");
        return NULL;
    }

Comment on lines +1132 to +1138
fd = open(rest_ctx_get_pkcs12_client_creds(rest_ctx), O_RDONLY);
if (fd == -1) {
ret = errno;
DEBUG(SSSDBG_CRIT_FAILURE,
"open() failed [%d][%s]\n", ret, strerror(ret));
goto done;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

open() is called on the result of rest_ctx_get_pkcs12_client_creds(rest_ctx) without checking if it is NULL. If the credentials path is NULL, this can lead to undefined behavior or a crash. A defensive NULL check should be added before calling open().

    const char *creds_path = rest_ctx_get_pkcs12_client_creds(rest_ctx);
    if (creds_path == NULL) {
        DEBUG(SSSDBG_CRIT_FAILURE, "PKCS#12 credentials path is NULL.\n");
        ret = EINVAL;
        goto done;
    }

    fd = open(creds_path, O_RDONLY);
    if (fd == -1) {
        ret = errno;
        DEBUG(SSSDBG_CRIT_FAILURE,
              "open() failed [%d][%s]\n", ret, strerror(ret));
        goto done;
    }

Comment thread src/oidc_child/libcrypto/oidc_child_get_jwk.c

@sssd-bot sssd-bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Review done using Claude Code / Opus 4.6

Functional Issues

Missing NULL checks after PKCS12_parse() — potential NULL pointer dereference

At src/oidc_child/libcrypto/oidc_child_get_jwk.c:574, PKCS12_parse() can return success (1) even when the PKCS#12 file does not contain a private key or certificate, leaving pkey or cert as NULL. The code proceeds to use both without NULL checks — cert at line 581 in get_cert_sha256_hash() and pkey at line 589 in EVP_PKEY_base_id(). This would cause a segfault on a malformed or certificate-only PKCS#12 file. Add NULL checks for both pkey and cert after the PKCS12_parse() call.

Missing NULL check before open() in get_jwk()

At src/oidc_child/oidc_child_json.c:1132, rest_ctx_get_pkcs12_client_creds(rest_ctx) is passed directly to open() without checking for NULL. While get_jwk() is currently only called from the CAM_JWT path where the PKCS#12 path is expected to be set, open(NULL, O_RDONLY) is undefined behavior. A defensive NULL check before the open() call would prevent a crash if this function is ever called from an unexpected path.

Potential double-free with json_pack "o" format on failure

At src/oidc_child/oidc_child_json.c:1235-1237, json_pack("{s:o}", "payload", payload_b64) uses the "o" format which steals the reference of payload_b64. Jansson's internal implementation calls json_object_set_new(), which decrements the reference count of the value even when the set operation fails. If json_pack fails, payload_b64 may already be freed, and the explicit json_decref(payload_b64) on line 1237 would cause a double-free. Consider switching to the "O" format (which increments the refcount) and always calling json_decref(payload_b64) afterward, or simply removing the json_decref(payload_b64) from the error block.

key_passwd stored as borrowed pointer without copy in rest_ctx

At src/oidc_child/oidc_child_curl.c:94, rest_ctx->key_passwd = key_passwd; stores a raw pointer without talloc_strdup. This is inconsistent with how ca_db (line 78) and pkcs12_client_creds (line 87) are handled in the same function — both are properly copied with talloc_strdup. In main() (src/oidc_child/oidc_child.c:986-991), free_cli_opts_members(&opts) frees opts.client_secret (which is the source of key_passwd) before talloc_free(main_ctx) frees the rest_ctx that still holds the pointer. While no code dereferences key_passwd during cleanup, this creates a dangling pointer window. The key_passwd should be talloc_strdup'd to match the pattern used for the other fields.

Nits & Non-Functional Issues

Test docstrings are generic and non-descriptive

The new system tests test_oidc_child__get_device_code, test_oidc_child__get_device_code_jwt, and test_oidc_child__get_device_code_mtls (at src/tests/system/tests/test_oidc_child.py:124,153,207) all have :title: Authenticate with default settings, which does not describe what the tests actually verify. The titles should reflect the specific feature being tested (e.g., "Request device code with client secret authentication", "Request device code with JWT client assertion", "Request device code with mTLS client authentication").

Function name append_to_creds_to_post_data has awkward phrasing

At src/oidc_child/oidc_child_curl.c:576, the function name reads awkwardly with the double "to". Consider append_creds_to_post_data for clarity.

PKCS#12 file buffer not securely erased in get_jwk()

At src/oidc_child/oidc_child_json.c:1159, buf holds the raw PKCS#12 file contents (encrypted key material) but is freed with plain talloc_free (line 1189) without setting sss_erase_talloc_mem_securely as a destructor. The rest of the code is careful about securely erasing sensitive data (e.g., sss_bn_to_base64 in oidc_child_get_jwk.c:94 and ec_priv_key_jwk at line 284). Consider adding talloc_set_destructor((void *) buf, sss_erase_talloc_mem_securely); after allocation for consistency.

mTLS test keystore cleanup fragile on failure

At src/tests/system/tests/test_oidc_child.py:260-262, the keystore cleanup (keytool -delete) is placed between the oidc_child invocation and the assertions. If the oidc_child command itself fails with an exception, the cleanup is skipped. The existing comment acknowledges this ("Currently the keystore is not restored by the framework"). A try/finally block or a pytest fixture with teardown would make this more robust.

Review of Existing Review Comments

Comment about NULL pointer dereferences after PKCS12_parse (gemini-code-assist) — Valid and confirmed. PKCS12_parse() can return success with NULL pkey/cert. This is covered above as a functional issue.

Comment about double-free with json_pack "o" format (gemini-code-assist) — Valid concern. The ownership semantics of the "o" format on json_pack failure make the explicit json_decref potentially dangerous. Covered above.

Comment about missing NULL check before open() (gemini-code-assist) — Valid. Covered above as a functional issue.

Comment about OpenSSL 3.0+ API compatibility (gemini-code-assist) — Addressed by the maintainer (@alexey-tikhonov) noting that support for older OpenSSL versions will be removed in a separate PR. No action needed here.

@alexey-tikhonov

Copy link
Copy Markdown
Member

PKCS#12 file buffer not securely erased in get_jwk()

It's encrypted so not a worry.

@alexey-tikhonov

Copy link
Copy Markdown
Member

Comment about NULL pointer dereferences after PKCS12_parse (gemini-code-assist) — Valid and confirmed. PKCS12_parse() can return success with NULL pkey/cert. This is covered above as a functional issue.

Comment about double-free with json_pack "o" format (gemini-code-assist) — Valid concern. The ownership semantics of the "o" format on json_pack failure make the explicit json_decref potentially dangerous. Covered above.

Comment about missing NULL check before open() (gemini-code-assist) — Valid. Covered above as a functional issue.

Probably 'master' branch needs an update...

@alexey-tikhonov

Copy link
Copy Markdown
Member

@sumit-bose, could you please cherry-pick #8785 here?

@alexey-tikhonov alexey-tikhonov added the no-backport This should go to target branch only. label Jun 8, 2026
@alexey-tikhonov
alexey-tikhonov removed the request for review from pbrezina June 8, 2026 13:32
To allow signing web tokens with the help of libjose the new function
get_jwk_from_pkcs12() can generate a JSON Web Key (JWK) from a given
PKCS#12 file.

Reviewed-by: Alexey Tikhonov <[email protected]>
Reviewed-by: Pavel Březina <[email protected]>
(cherry picked from commit 8100381)
With the new option pkcs12-client-creds a PKCS#12 file with certificate
and private key can be specified for client authentication. The client
credential will be used as a password to unlock the key in the PKCS#12
file. The PKCS#12 file is used in a way to make mutual TLS (mTLS) work
with an IdP.

:relnote: new oidc_child option --pkcs12-client-creds to specify the
path to a PKCS#12 file with certificate and private key for certificate
based authentication. Password to unlock the private key can be given
with --client-secret or --client-secret-stdin options.

Reviewed-by: Alexey Tikhonov <[email protected]>
Reviewed-by: Pavel Březina <[email protected]>
(cherry picked from commit ec078ea)
With the new option --client-auth-method oidc_child can select between
authentication with a client secret, mutual-TLS/mTLS (RFC-8705) and JWT
client assertion (RFC-7523). The latter two require a PKCS#12 file with
the client credentials (certificate and private key) and the password to
unlock the private key must be provided with the --client-secret or
--client-secret-stdin option.

:relnote: new oidc_child option --client-auth-method to select between
authentication with client secret, mutual-TLS/mTLS (RFC-8705) and JWT
client assertion (RFC-7523). For mTLS all key types supported by libcurl
can be used. For JWT RS256, ES256, ES384 and ES512 with matching key
types are supported.

Reviewed-by: Alexey Tikhonov <[email protected]>
Reviewed-by: Pavel Březina <[email protected]>
(cherry picked from commit 6577434)
This new test call oidc_child with the '--get-device-code' option with
different client authentication methods.

Reviewed-by: Alexey Tikhonov <[email protected]>
Reviewed-by: Pavel Březina <[email protected]>
(cherry picked from commit 9a3487d)
The reference is always stolen if the 'o' format specifier is used even
in the case of errors.

Reviewed-by: Alexey Tikhonov <[email protected]>
(cherry picked from commit f72a7a6)
Reviewed-by: Alexey Tikhonov <[email protected]>
(cherry picked from commit 44cd06b)
Reviewed-by: Alexey Tikhonov <[email protected]>
(cherry picked from commit c2f9fff)
@sssd-bot

sssd-bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor Author

The pull request was accepted by @alexey-tikhonov with the following PR CI status:


🟢 rpm-build:centos-stream-10-x86_64:upstream (success)
🟢 rpm-build:fedora-43-x86_64:upstream (success)
🟢 rpm-build:fedora-44-x86_64:upstream (success)
🟢 rpm-build:fedora-rawhide-x86_64:upstream (success)
🟢 testing-farm:fedora-43-x86_64:upstream (success)
🟢 testing-farm:fedora-44-x86_64:centos-stream-10 (success)
🟢 testing-farm:fedora-44-x86_64:upstream (success)
🔴 testing-farm:fedora-rawhide-x86_64:upstream (failure)
🟢 Build / freebsd (success)
🟢 Build / make-distcheck (success)


There are unsuccessful or unfinished checks. Make sure that the failures are not related to this pull request before merging.

@sssd-bot
sssd-bot force-pushed the SSSD-sssd-backport-pr8708-to-sssd-2-13 branch from dd2aa05 to 95c9fe8 Compare June 8, 2026 13:33
@alexey-tikhonov
alexey-tikhonov merged commit cd3809b into SSSD:sssd-2-13 Jun 8, 2026
3 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Accepted no-backport This should go to target branch only.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants