Skip to content

Tests: Smart card authentication tests for SSSD's CKF_PROTECTED_AUTH…#8744

Open
krishnavema wants to merge 1 commit into
SSSD:masterfrom
krishnavema:pinpad-tests
Open

Tests: Smart card authentication tests for SSSD's CKF_PROTECTED_AUTH…#8744
krishnavema wants to merge 1 commit into
SSSD:masterfrom
krishnavema:pinpad-tests

Conversation

@krishnavema

@krishnavema krishnavema commented May 28, 2026

Copy link
Copy Markdown
Contributor

Smart card authentication tests for SSSD's CKF_PROTECTED_AUTHENTICATION_PATH (hardware pinpad) support, verifying that p11_child detects the flag, passes NULL PIN to C_Login, and PAM prompts for external keypad
entry instead of keyboard PIN input.

@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 a comprehensive suite of system tests for SSSD smart card pinpad authentication, covering scenarios such as successful login via su, correct external keypad prompting, local user authentication, and keyboard-less PIN entry. The review feedback correctly identifies a security vulnerability where sensitive private key files are written to /tmp with default permissions and are not cleaned up after execution. It is recommended to restrict these files to 0600 permissions and ensure they are deleted in a finally block to prevent credential leakage.

Comment on lines +134 to +161
client.fs.write(pem_cert, cert_content)
client.fs.write(pem_key, key_content)

x509_args: CLIBuilderArgs = {
"in": (client.host.cli.option.VALUE, pem_cert),
"outform": (client.host.cli.option.VALUE, "DER"),
"out": (client.host.cli.option.VALUE, der_cert),
}
client.host.conn.run(client.host.cli.command("openssl x509", x509_args))

rsa_args: CLIBuilderArgs = {
"in": (client.host.cli.option.VALUE, pem_key),
"outform": (client.host.cli.option.VALUE, "DER"),
"out": (client.host.cli.option.VALUE, der_key),
}
client.host.conn.run(client.host.cli.command("openssl rsa", rsa_args))

for obj_path, obj_type in [(der_key, "privkey"), (der_cert, "cert")]:
args: CLIBuilderArgs = {
"module": (client.host.cli.option.VALUE, OPENSC_MODULE),
"login": (client.host.cli.option.SWITCH, True),
"pin": (client.host.cli.option.VALUE, pin),
"write-object": (client.host.cli.option.VALUE, obj_path),
"type": (client.host.cli.option.VALUE, obj_type),
"id": (client.host.cli.option.VALUE, cert_id),
"label": (client.host.cli.option.VALUE, username),
}
client.host.conn.run(client.host.cli.command("pkcs11-tool", args))

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-high high

The private key files are written to /tmp with default permissions, which can make them readable by other local users on the system. Additionally, these temporary files are never cleaned up from the filesystem after the test completes, leading to sensitive credential leakage.

To resolve this, restrict the permissions of the private key files to 0600 immediately after creation, and ensure all temporary files are cleaned up in a finally block.

    try:
        client.fs.write(pem_cert, cert_content)
        client.fs.write(pem_key, key_content)
        client.host.conn.run(f"chmod 600 {pem_key}", raise_on_error=False)

        x509_args: CLIBuilderArgs = {
            "in": (client.host.cli.option.VALUE, pem_cert),
            "outform": (client.host.cli.option.VALUE, "DER"),
            "out": (client.host.cli.option.VALUE, der_cert),
        }
        client.host.conn.run(client.host.cli.command("openssl x509", x509_args))

        rsa_args: CLIBuilderArgs = {
            "in": (client.host.cli.option.VALUE, pem_key),
            "outform": (client.host.cli.option.VALUE, "DER"),
            "out": (client.host.cli.option.VALUE, der_key),
        }
        client.host.conn.run(client.host.cli.command("openssl rsa", rsa_args))
        client.host.conn.run(f"chmod 600 {der_key}", raise_on_error=False)

        for obj_path, obj_type in [(der_key, "privkey"), (der_cert, "cert")]:
            args: CLIBuilderArgs = {
                "module": (client.host.cli.option.VALUE, OPENSC_MODULE),
                "login": (client.host.cli.option.SWITCH, True),
                "pin": (client.host.cli.option.VALUE, pin),
                "write-object": (client.host.cli.option.VALUE, obj_path),
                "type": (client.host.cli.option.VALUE, obj_type),
                "id": (client.host.cli.option.VALUE, cert_id),
                "label": (client.host.cli.option.VALUE, username),
            }
            client.host.conn.run(client.host.cli.command("pkcs11-tool", args))
    finally:
        client.host.conn.run(f"rm -f {pem_cert} {pem_key} {der_cert} {der_key}", raise_on_error=False)

Comment on lines +456 to +529
try:
req_args: CLIBuilderArgs = {
"x509": (client.host.cli.option.SWITCH, True),
"nodes": (client.host.cli.option.SWITCH, True),
"sha256": (client.host.cli.option.SWITCH, True),
"days": (client.host.cli.option.VALUE, "365"),
"newkey": (client.host.cli.option.VALUE, "rsa:2048"),
"keyout": (client.host.cli.option.VALUE, key_pem),
"out": (client.host.cli.option.VALUE, cert_pem),
"subj": (client.host.cli.option.VALUE, "/CN=Pinpad Test Cert"),
}
client.host.conn.run(client.host.cli.command("openssl req", req_args))

x509_args: CLIBuilderArgs = {
"in": (client.host.cli.option.VALUE, cert_pem),
"outform": (client.host.cli.option.VALUE, "DER"),
"out": (client.host.cli.option.VALUE, cert_der),
}
client.host.conn.run(client.host.cli.command("openssl x509", x509_args))

rsa_args: CLIBuilderArgs = {
"in": (client.host.cli.option.VALUE, key_pem),
"outform": (client.host.cli.option.VALUE, "DER"),
"out": (client.host.cli.option.VALUE, key_der),
}
client.host.conn.run(client.host.cli.command("openssl rsa", rsa_args))

for obj_path, obj_type in [(key_der, "privkey"), (cert_der, "cert")]:
args: CLIBuilderArgs = {
"module": (client.host.cli.option.VALUE, OPENSC_MODULE),
"login": (client.host.cli.option.SWITCH, True),
"pin": (client.host.cli.option.VALUE, TOKEN_PIN),
"write-object": (client.host.cli.option.VALUE, obj_path),
"type": (client.host.cli.option.VALUE, obj_type),
"id": (client.host.cli.option.VALUE, cert_id),
"label": (client.host.cli.option.VALUE, username),
}
client.host.conn.run(client.host.cli.command("pkcs11-tool", args))

ensure_pcscd_accessible(client)

client.host.fs.rm("/etc/sssd/pki/sssd_auth_ca_db.pem")
cert_data = client.host.fs.read(cert_pem)
client.host.fs.append("/etc/sssd/pki/sssd_auth_ca_db.pem", cert_data)

client.authselect.select("sssd", ["with-smartcard"])
client.sssd.common.local()
client.sssd.dom("local")["local_auth_policy"] = "only"
client.sssd.section(f"certmap/local/{username}")["matchrule"] = (
"<SUBJECT>.*CN=Pinpad Test Cert.*"
)
client.sssd.pam["pam_cert_auth"] = "True"
client.sssd.pam["p11_child_timeout"] = "60"
client.sssd.start(debug_level="0xFFF0", check_config=False)

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

output = result.stdout + result.stderr
assert "Use external keypad" in output, (
f"Expected 'Use external keypad' prompt but got: "
f"stdout={result.stdout!r}, stderr={result.stderr!r}"
)
assert result.rc == 0, (
f"Authentication failed with rc={result.rc}, "
f"stdout={result.stdout!r}, stderr={result.stderr!r}"
)
assert username in result.stdout, (
f"'{username}' not found in whoami output: {result.stdout}"
)
finally:
cleanup_hw_card(client, cert_id=cert_id)

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

The generated self-signed private key files are written to /tmp with default permissions and are never cleaned up from the filesystem after the test completes, leading to sensitive credential leakage.

To resolve this, restrict the permissions of the private key files to 0600 immediately after creation, and ensure all temporary files are cleaned up in the finally block.

    try:
        req_args: CLIBuilderArgs = {
            "x509": (client.host.cli.option.SWITCH, True),
            "nodes": (client.host.cli.option.SWITCH, True),
            "sha256": (client.host.cli.option.SWITCH, True),
            "days": (client.host.cli.option.VALUE, "365"),
            "newkey": (client.host.cli.option.VALUE, "rsa:2048"),
            "keyout": (client.host.cli.option.VALUE, key_pem),
            "out": (client.host.cli.option.VALUE, cert_pem),
            "subj": (client.host.cli.option.VALUE, "/CN=Pinpad Test Cert"),
        }
        client.host.conn.run(client.host.cli.command("openssl req", req_args))
        client.host.conn.run(f"chmod 600 {key_pem}", raise_on_error=False)

        x509_args: CLIBuilderArgs = {
            "in": (client.host.cli.option.VALUE, cert_pem),
            "outform": (client.host.cli.option.VALUE, "DER"),
            "out": (client.host.cli.option.VALUE, cert_der),
        }
        client.host.conn.run(client.host.cli.command("openssl x509", x509_args))

        rsa_args: CLIBuilderArgs = {
            "in": (client.host.cli.option.VALUE, key_pem),
            "outform": (client.host.cli.option.VALUE, "DER"),
            "out": (client.host.cli.option.VALUE, key_der),
        }
        client.host.conn.run(client.host.cli.command("openssl rsa", rsa_args))
        client.host.conn.run(f"chmod 600 {key_der}", raise_on_error=False)

        for obj_path, obj_type in [(key_der, "privkey"), (cert_der, "cert")]:
            args: CLIBuilderArgs = {
                "module": (client.host.cli.option.VALUE, OPENSC_MODULE),
                "login": (client.host.cli.option.SWITCH, True),
                "pin": (client.host.cli.option.VALUE, TOKEN_PIN),
                "write-object": (client.host.cli.option.VALUE, obj_path),
                "type": (client.host.cli.option.VALUE, obj_type),
                "id": (client.host.cli.option.VALUE, cert_id),
                "label": (client.host.cli.option.VALUE, username),
            }
            client.host.conn.run(client.host.cli.command("pkcs11-tool", args))

        ensure_pcscd_accessible(client)

        client.host.fs.rm("/etc/sssd/pki/sssd_auth_ca_db.pem")
        cert_data = client.host.fs.read(cert_pem)
        client.host.fs.append("/etc/sssd/pki/sssd_auth_ca_db.pem", cert_data)

        client.authselect.select("sssd", ["with-smartcard"])
        client.sssd.common.local()
        client.sssd.dom("local")["local_auth_policy"] = "only"
        client.sssd.section(f"certmap/local/{username}")["matchrule"] = (
            "<SUBJECT>.*CN=Pinpad Test Cert.*"
        )
        client.sssd.pam["pam_cert_auth"] = "True"
        client.sssd.pam["p11_child_timeout"] = "60"
        client.sssd.start(debug_level="0xFFF0", check_config=False)

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

        output = result.stdout + result.stderr
        assert "Use external keypad" in output, (
            f"Expected 'Use external keypad' prompt but got: "
            f"stdout={result.stdout!r}, stderr={result.stderr!r}"
        )
        assert result.rc == 0, (
            f"Authentication failed with rc={result.rc}, "
            f

@krishnavema krishnavema force-pushed the pinpad-tests branch 2 times, most recently from 3d8ac0b to d0a8fd2 Compare June 1, 2026 03:55
@krishnavema krishnavema requested a review from sumit-bose June 1, 2026 08:16
@krishnavema krishnavema marked this pull request as ready for review June 1, 2026 08:16
@krishnavema krishnavema marked this pull request as draft June 2, 2026 14:22
@krishnavema krishnavema marked this pull request as ready for review June 4, 2026 13:19
@krishnavema

krishnavema commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

Removed the cleanup_hw_card and enroll_to_hw_card methods that were corrupting the smart card on every run, and consolidated five redundant tests into a single local-user test that covers all SSSD pinpad
code paths — CKF_PROTECTED_AUTHENTICATION_PATH detection, PAM prompt format, and p11_child login — without ever writing to or cleaning the physical card

Test result:
tests/test_smartcard_pinpad.py::test_smartcard_pinpad__su_as_local_user (client) PASSED [100%]

Comment thread src/tests/system/tests/test_smartcard_pinpad.py Outdated
Comment thread src/tests/system/tests/test_smartcard_pinpad.py Outdated
@krishnavema krishnavema requested a review from sumit-bose June 10, 2026 08:30
@alexey-tikhonov alexey-tikhonov requested a review from spoore1 June 11, 2026 12:54
@alexey-tikhonov alexey-tikhonov added the no-backport This should go to target branch only. label Jun 11, 2026

OPENSC_MODULE = "/usr/lib64/pkcs11/opensc-pkcs11.so"
P11_CHILD_LOG = "/var/log/sssd/p11_child.log"
TOKEN_LABEL = "MyEID"

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.

Hi,

I think a more generic label like e.g. "SSSD Test Token" or similar would be better.

bye,
Sumit

if slot_result.rc != 0 or "token" not in slot_result.stdout.lower():
pytest.skip("Smart card reader found but no token present — insert a card")

if TOKEN_LABEL.lower() not in slot_result.stdout.lower():

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.

Hi,

maybe you can use a more specific match. Since the line you are interested in the the line with the PKCS#11 URI you can at least use something like `f"token={TOKEN_LABEL.lower()}"?

bye,
Sumit

)


def ensure_pcscd_accessible(client: Client) -> None:

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.

Hi,

if you run all the check in detect_pinpad_reader() as sssd user you can drop this method.

bye,
Sumit

3. No ``C_Login failed`` in the p11_child log
:customerscenario: True
"""
detect_pinpad_reader(client)

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.

Hi,

I think you removed some useful steps. I think it is ok to write and remove certificate and key to the card as long as the fixed TOKEN_LABEL is used to not accidentally overwrite some other certificate from a different token.

Assuming the card already has an expected certificate stored would lower the benefit of the test. If the test write the data to the card you only have to initialize a test card with the expected token label and then you can run the test. Otherwise you have to create the key and certificate (maybe even the CA), write them to the card manually and make the client aware of the CA certificate so that the validation does not fail.

Additionally, you removed the method which sets enable_pinpad = true in opensc.conf. This should be re-added as well.

bye,
Sumit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-backport This should go to target branch only.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants