diff --git a/src/tests/system/requirements.txt b/src/tests/system/requirements.txt index 788c9285d5..6de8d10414 100644 --- a/src/tests/system/requirements.txt +++ b/src/tests/system/requirements.txt @@ -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 diff --git a/src/tests/system/tests/test_smartcard.py b/src/tests/system/tests/test_smartcard.py index bad58979b5..171c924033 100644 --- a/src/tests/system/tests/test_smartcard.py +++ b/src/tests/system/tests/test_smartcard.py @@ -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" + + +def enroll_to_token( + 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) @@ -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) + + +@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)