Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/p11_child/p11_child_common.c
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,13 @@ int main(int argc, const char *argv[])
timeout = -1;
}

/* When soft_ocsp is enabled and no timeout was specified, use a sensible
* default so that the OCSP connect does not block indefinitely when the
* responder is unreachable (RHEL-5043). */
if (cert_verify_opts->soft_ocsp && timeout == -1) {
timeout = 10;
}

ret = do_work(main_ctx, mode, ca_db, cert_verify_opts, wait_for_card,
cert_b64, pin, module_name, token_name, key_id, label, uri,
timeout, &multi);
Expand Down
27 changes: 8 additions & 19 deletions src/p11_child/p11_child_openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ struct p11_ctx {
const char *ca_db;
bool wait_for_card;
struct cert_verify_opts *cert_verify_opts;
time_t ocsp_deadline;
time_t timeout;
};

static OCSP_RESPONSE *query_responder(BIO *cbio, const char *host,
Expand Down Expand Up @@ -381,14 +381,12 @@ static errno_t do_ocsp(struct p11_ctx *p11_ctx, X509 *cert)

OCSP_request_add1_nonce(ocsp_req, NULL, -1);

if (p11_ctx->ocsp_deadline != -1 && p11_ctx->cert_verify_opts->soft_ocsp) {
req_timeout = p11_ctx->ocsp_deadline - time(NULL);
if (req_timeout <= 0) {
/* no time left for OCSP */
DEBUG(SSSDBG_TRACE_INTERNAL,
"Timeout before we could run OCSP request.\n");
req_timeout = 0;
}
if (p11_ctx->cert_verify_opts->soft_ocsp) {
Comment thread
krishnavema marked this conversation as resolved.
/* soft_ocsp requires a finite timeout so that p11_child does not
* block indefinitely when the OCSP responder is unreachable.
* The timeout is guaranteed to be >= 0 at this point because
* main() sets it to 10 when soft_ocsp is enabled (RHEL-5043). */
req_timeout = (int)p11_ctx->timeout;
}
if (req_timeout != 0) {
ocsp_resp = process_responder(ocsp_req, host, path, port, use_ssl,
Expand Down Expand Up @@ -594,16 +592,6 @@ errno_t init_p11_ctx(TALLOC_CTX *mem_ctx, const char *ca_db,
return ENOMEM;
}

if (timeout == 1) {
/* timeout of 1 sec is too short (see -1 in deadline calculation),
* increasing to 2 and hope that the ocsp operation finishes
* before p11_child is terminated.
*/
timeout = 2;
}
/* timeout <= 0 means no timeout specified */
ctx->ocsp_deadline = timeout > 0 ? time(NULL) + timeout - 1 : -1;

/* See https://wiki.openssl.org/index.php/Library_Initialization for
* details. */
ret = OPENSSL_init_ssl(0, NULL);
Expand All @@ -615,6 +603,7 @@ errno_t init_p11_ctx(TALLOC_CTX *mem_ctx, const char *ca_db,

ctx->ca_db = ca_db;
ctx->wait_for_card = wait_for_card;
ctx->timeout = timeout;
talloc_set_destructor(ctx, p11_ctx_destructor);

*p11_ctx = ctx;
Expand Down
3 changes: 2 additions & 1 deletion src/tests/system/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ git+https://github.com/next-actions/pytest-mh
git+https://github.com/next-actions/pytest-ticket
git+https://github.com/next-actions/pytest-tier
git+https://github.com/next-actions/pytest-output
git+https://github.com/SSSD/sssd-test-framework
#git+https://github.com/SSSD/sssd-test-framework
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.

This must be reverted before merging

git+https://github.com/krishnavema/sssd-test-framework@smartcard-pkcs11
174 changes: 174 additions & 0 deletions src/tests/system/tests/test_smartcard.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,56 @@

import pytest
from sssd_test_framework.roles.client import Client
from sssd_test_framework.roles.ipa import IPA
from sssd_test_framework.topology import KnownTopology


def _enroll_ipa_smartcard(client: Client, ipa: IPA, username: str) -> None:
"""Request a certificate from the IPA CA and load it into a SoftHSM token."""
cert, key, _ = ipa.ca.request(username)

cert_content = ipa.fs.read(cert)
key_content = ipa.fs.read(key)
client.fs.write(f"/opt/test_ca/{username}.crt", cert_content)
client.fs.write(f"/opt/test_ca/{username}.key", key_content)

client.smartcard.initialize_card()
client.smartcard.add_key(f"/opt/test_ca/{username}.key")
client.smartcard.add_cert(f"/opt/test_ca/{username}.crt")


def _configure_ipa_smartcard_and_start(
client: Client,
*,
certificate_verification: str | None = None,
) -> None:
"""Configure SSSD for IPA smart-card authentication and present a virtual card."""
client.authselect.select("sssd", ["with-smartcard"])

if certificate_verification is not None:
client.sssd.sssd["certificate_verification"] = certificate_verification

client.sssd.pam["pam_cert_auth"] = "True"
client.sssd.start()
client.svc.restart("virt_cacard.service")


def _assert_smartcard_auth(client: Client, username: str) -> None:
"""Run the double-``su`` pattern and verify PIN-based authentication."""
result = client.host.conn.run(
f"su - {username} -c 'su - {username} -c whoami'",
input="123456",
)
assert "PIN" in result.stderr, f"String 'PIN' was not found in stderr! Stderr content: {result.stderr}"
assert username in result.stdout, f"'{username}' not found in 'whoami' output! Stdout content: {result.stdout}"


def _redirect_ocsp_responder(client: Client, ipa: IPA, target_ip: str) -> None:
"""Point the IPA OCSP responder hostname to *target_ip* via ``/etc/hosts``."""
ipa_ca_hostname = f"ipa-ca.{ipa.domain}"
client.fs.append("/etc/hosts", f"\n{target_ip} {ipa_ca_hostname}\n")


@pytest.mark.importance("critical")
@pytest.mark.topology(KnownTopology.Client)
@pytest.mark.builtwith(client="virtualsmartcard")
Expand All @@ -30,3 +77,130 @@ def test_smartcard__su_as_local_user(client: Client):
result = client.host.conn.run("su - localuser1 -c 'su - localuser1 -c whoami'", input="123456")
assert "PIN" in result.stderr, "String 'PIN' was not found in stderr!"
assert "localuser1" in result.stdout, "'localuser1' not found in 'whoami' output!"


@pytest.mark.ticket(jira="RHEL-5043")
@pytest.mark.importance("high")
@pytest.mark.topology(KnownTopology.IPA)
@pytest.mark.builtwith(client="virtualsmartcard")
def test_smartcard__soft_ocsp_with_unreachable_responder(client: Client, ipa: IPA):
"""
:title: Smart card authentication succeeds with soft_ocsp when OCSP responder is unreachable
:setup:
1. Create an IPA user and enroll a smart card.
2. Configure ``certificate_verification = soft_ocsp``.
3. Point ipa-ca to 192.168.123.1 (non-routable, packets silently dropped).
4. Start SSSD and present the virtual smart card.
:steps:
1. Authenticate via ``su`` with the smart card PIN.
:expectedresults:
1. PIN prompt appears and authentication succeeds despite the
unreachable OCSP responder.
:customerscenario: True
"""
username = "smartcarduser1"

ipa.user(username).add()
_enroll_ipa_smartcard(client, ipa, username)

_redirect_ocsp_responder(client, ipa, "192.168.123.1")
_configure_ipa_smartcard_and_start(client, certificate_verification="soft_ocsp")

_assert_smartcard_auth(client, username)


@pytest.mark.ticket(jira="RHEL-5043")
@pytest.mark.importance("high")
@pytest.mark.topology(KnownTopology.IPA)
@pytest.mark.builtwith(client="virtualsmartcard")
def test_smartcard__soft_ocsp_with_reachable_responder(client: Client, ipa: IPA):
"""
:title: Smart card authentication succeeds with soft_ocsp when OCSP responder is reachable
:setup:
1. Create an IPA user and enroll a smart card.
2. Configure ``certificate_verification = soft_ocsp``.
3. Start SSSD and present the virtual smart card (OCSP responder is reachable).
:steps:
1. Authenticate via ``su`` with the smart card PIN.
:expectedresults:
1. PIN prompt appears and authentication succeeds; the OCSP check
completes normally.
:customerscenario: True
"""
username = "smartcarduser2"

ipa.user(username).add()
_enroll_ipa_smartcard(client, ipa, username)

_configure_ipa_smartcard_and_start(client, certificate_verification="soft_ocsp")

_assert_smartcard_auth(client, username)


@pytest.mark.ticket(jira="RHEL-5043")
@pytest.mark.importance("high")
@pytest.mark.topology(KnownTopology.IPA)
@pytest.mark.builtwith(client="virtualsmartcard")
def test_smartcard__soft_ocsp_with_connection_refused(client: Client, ipa: IPA):
"""
:title: Smart card authentication succeeds with soft_ocsp when OCSP connection is refused
:setup:
1. Create an IPA user and enroll a smart card.
2. Configure ``certificate_verification = soft_ocsp``.
3. Point ipa-ca to 127.0.0.7 (loopback, immediate TCP RST).
4. Start SSSD and present the virtual smart card.
:steps:
1. Authenticate via ``su`` with the smart card PIN.
:expectedresults:
1. PIN prompt appears and authentication succeeds; the OCSP
connection is immediately refused and soft_ocsp skips the check.
:customerscenario: True
"""
username = "smartcarduser3"

ipa.user(username).add()
_enroll_ipa_smartcard(client, ipa, username)

_redirect_ocsp_responder(client, ipa, "127.0.0.7")
_configure_ipa_smartcard_and_start(client, certificate_verification="soft_ocsp")

_assert_smartcard_auth(client, username)


@pytest.mark.ticket(jira="RHEL-5043")
@pytest.mark.importance("high")
@pytest.mark.topology(KnownTopology.IPA)
@pytest.mark.builtwith(client="virtualsmartcard")
def test_smartcard__without_soft_ocsp_with_unreachable_responder(client: Client, ipa: IPA):
"""
:title: Smart card authentication fails without soft_ocsp when OCSP responder is unreachable
:setup:
1. Create an IPA user and enroll a smart card.
2. Do NOT set ``certificate_verification`` (default OCSP behaviour).
3. Point ipa-ca to 192.168.123.1 (unreachable).
4. Start SSSD and present the virtual smart card.
:steps:
1. Attempt to authenticate via ``su`` with the smart card PIN.
:expectedresults:
1. Without ``soft_ocsp``, the certificate check fails because the
OCSP responder is unreachable. The user sees a password prompt
(not a PIN prompt) or the authentication fails outright.
:customerscenario: True
"""
username = "smartcarduser4"

ipa.user(username).add()
_enroll_ipa_smartcard(client, ipa, username)

_redirect_ocsp_responder(client, ipa, "192.168.123.1")
_configure_ipa_smartcard_and_start(client, certificate_verification=None)

result = client.host.conn.run(
f"su - {username} -c 'su - {username} -c whoami'",
input="123456",
raise_on_error=False,
)

assert (
"PIN" not in result.stderr or result.rc != 0
), f"Expected authentication to fail without soft_ocsp when OCSP is unreachable! rc={result.rc}"
Loading