Skip to content
Draft
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
2 changes: 1 addition & 1 deletion src/tests/system/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ 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/krishnavema/sssd-test-framework@multi-token-smart-card-support
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

This change introduces a dependency on a personal fork (krishnavema/sssd-test-framework). While this might be acceptable for development, it poses a security and maintenance risk for the main branch. The changes from this fork should be merged into the upstream SSSD/sssd-test-framework repository, and the dependency should point to an official release or commit from the upstream repository before this pull request is merged.

git+https://github.com/SSSD/sssd-test-framework

160 changes: 160 additions & 0 deletions src/tests/system/tests/test_smartcard.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,78 @@

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

TOKEN1_LABEL = "SC_Token_1"
TOKEN2_LABEL = "SC_Token_2"
TOKEN_PIN = "123456"
Comment thread
spoore1 marked this conversation as resolved.


def enroll_to_token(
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.

I'm wondering now if this would be better to add this to the smartcard util in the framework with this: SSSD/sssd-test-framework#239

Also, if this is moved to the framework, can the initialize_card() be included here to make this more generic?

client: Client,
ipa: IPA,
username: str,
*,
token_label: str,
cert_id: str = "01",
pin: str = TOKEN_PIN,
) -> None:
"""
Request an IPA-signed certificate for *username* and store it on *token_label*.

:param client: Client role object.
:type client: Client
:param ipa: IPA role object whose CA issues the certificate.
:type ipa: IPA
:param username: IPA principal to issue the certificate for.
:type username: str
:param token_label: SoftHSM token label to write the objects to.
:type token_label: str
:param cert_id: PKCS#11 object ID, defaults to "01".
:type cert_id: str, optional
:param pin: User PIN for the token, defaults to TOKEN_PIN.
:type pin: str, optional
"""
cert, key, _ = ipa.ca.request(username)
cert_content = ipa.fs.read(cert)
key_content = ipa.fs.read(key)

cert_path = f"/opt/test_ca/{username}_{token_label}.crt"
key_path = f"/opt/test_ca/{username}_{token_label}.key"

client.fs.write(cert_path, cert_content)
client.fs.write(key_path, key_content)

client.smartcard.add_key(key_path, key_id=cert_id, pin=pin, token_label=token_label, label=username)
client.smartcard.add_cert(cert_path, cert_id=cert_id, pin=pin, token_label=token_label, label=username)


def setup_two_tokens(
client: Client,
ipa: IPA,
*,
token1_username: str,
token2_username: str,
) -> None:
"""
Create two SoftHSM tokens, each holding an IPA-signed certificate.

:param client: Client role object.
:type client: Client
:param ipa: IPA role object.
:type ipa: IPA
:param token1_username: IPA user whose cert goes onto token 1.
:type token1_username: str
:param token2_username: IPA user whose cert goes onto token 2.
:type token2_username: str
"""
client.smartcard.initialize_card(label=TOKEN1_LABEL, user_pin=TOKEN_PIN)
enroll_to_token(client, ipa, token1_username, token_label=TOKEN1_LABEL)

client.smartcard.initialize_card(label=TOKEN2_LABEL, user_pin=TOKEN_PIN, reset=False)
enroll_to_token(client, ipa, token2_username, token_label=TOKEN2_LABEL)


@pytest.mark.importance("critical")
@pytest.mark.topology(KnownTopology.Client)
Expand All @@ -30,3 +100,93 @@ 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.importance("critical")
@pytest.mark.topology(KnownTopology.IPA)
@pytest.mark.builtwith(client="virtualsmartcard")
def test_smartcard__two_tokens_match_on_first(client: Client, ipa: IPA):
"""
:title: Two smart cards – valid certificate on the first token
:setup:
1. Create IPA user and a decoy IPA user
2. Initialize two SoftHSM tokens (simulating two smart cards)
3. Place the target user's IPA certificate on token 1
4. Place the decoy user's IPA certificate on token 2
5. Configure SSSD for smart card authentication and start services
:steps:
1. Authenticate as the target IPA user via nested ``su`` with the
smart card PIN
:expectedresults:
1. SSSD's ``p11_child`` finds valid certificates on both tokens,
SSSD maps the token-1 certificate to the target user, prompts
for PIN, and authentication succeeds
:customerscenario: True
"""
username = "scuser_t1"
decoy = "scdecoy_t1"
ipa.user(username).add()
ipa.user(decoy).add()

setup_two_tokens(client, ipa, token1_username=username, token2_username=decoy)
client.sssd.common.smartcard_with_softhsm(client.smartcard)
assert client.auth.su.smartcard_with_su(username, TOKEN_PIN)
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.

Just a note to change this if you rename in SSSD/sssd-test-framework#239



@pytest.mark.importance("critical")
@pytest.mark.topology(KnownTopology.IPA)
@pytest.mark.builtwith(client="virtualsmartcard")
def test_smartcard__two_tokens_match_on_second(client: Client, ipa: IPA):
"""
:title: Two smart cards – valid certificate only on the second token
:setup:
1. Create IPA user and a decoy IPA user
2. Initialize two SoftHSM tokens (simulating two smart cards)
3. Place the decoy user's IPA certificate on token 1
4. Place the target user's IPA certificate on token 2
5. Configure SSSD for smart card authentication and start services
:steps:
1. Authenticate as the target IPA user via nested ``su`` with the
smart card PIN
:expectedresults:
1. SSSD's ``p11_child`` does **not** stop at token 1 (whose cert
maps to the decoy user); it continues to token 2, finds the
certificate that maps to the target user, prompts for PIN, and
authentication succeeds
:customerscenario: True
"""
username = "scuser_t2"
decoy = "scdecoy_t2"
ipa.user(username).add()
ipa.user(decoy).add()

setup_two_tokens(client, ipa, token1_username=decoy, token2_username=username)
client.sssd.common.smartcard_with_softhsm(client.smartcard)
assert client.auth.su.smartcard_with_su(username, TOKEN_PIN)


@pytest.mark.importance("critical")
@pytest.mark.topology(KnownTopology.IPA)
@pytest.mark.builtwith(client="virtualsmartcard")
def test_smartcard__two_tokens_match_on_both(client: Client, ipa: IPA):
"""
:title: Two smart cards – valid certificate on both tokens
:setup:
1. Create IPA user
2. Initialize two SoftHSM tokens (simulating two smart cards)
3. Place a valid IPA certificate for the same user on both tokens
4. Configure SSSD for smart card authentication and start services
:steps:
1. Authenticate as the IPA user via nested ``su`` with the PIN of
the first token
:expectedresults:
1. SSSD's ``p11_child`` finds valid certificates on both tokens and
authentication succeeds regardless of which token is tried first
:customerscenario: True
"""
username = "scuser_both"
ipa.user(username).add()

setup_two_tokens(client, ipa, token1_username=username, token2_username=username)
client.sssd.common.smartcard_with_softhsm(client.smartcard)
assert client.auth.su.smartcard_with_su(username, TOKEN_PIN, num_certs=2)
Loading