diff --git a/sssd_test_framework/utils/smartcard.py b/sssd_test_framework/utils/smartcard.py index 026dd6f5..328fcff2 100644 --- a/sssd_test_framework/utils/smartcard.py +++ b/sssd_test_framework/utils/smartcard.py @@ -76,12 +76,43 @@ def initialize_card(self, label: str = "sc_test", so_pin: str = "12345678", user self.cli.command("softhsm2-util --init-token", args), env={"SOFTHSM2_CONF": self.SOFTHSM2_CONF_PATH} ) + def initialize_additional_token(self, label: str, so_pin: str = "12345678", user_pin: str = "123456") -> None: + """ + Initializes an additional SoftHSM token without wiping existing tokens. + + Unlike :meth:`initialize_card`, this method does **not** remove the + existing token storage directory. It simply calls + ``softhsm2-util --init-token --free`` with the given *label* so that a + new slot is allocated alongside any tokens that are already present. + + Use this after an initial :meth:`initialize_card` call to create a + multi-token environment (simulating multiple physical smart cards). + + :param label: Token label (must be unique among existing tokens). + :type label: str + :param so_pin: Security Officer PIN, defaults to "12345678" + :type so_pin: str, optional + :param user_pin: User PIN, defaults to "123456" + :type user_pin: str, optional + """ + args: CLIBuilderArgs = { + "label": (self.cli.option.VALUE, label), + "free": (self.cli.option.SWITCH, True), + "so-pin": (self.cli.option.VALUE, so_pin), + "pin": (self.cli.option.VALUE, user_pin), + } + self.host.conn.run( + self.cli.command("softhsm2-util --init-token", args), env={"SOFTHSM2_CONF": self.SOFTHSM2_CONF_PATH} + ) + def add_cert( self, cert_path: str, cert_id: str = "01", pin: str = "123456", private: bool | None = False, + token_label: str | None = None, + label: str | None = None, ) -> None: """ Adds a certificate or private key to the smart card. @@ -94,6 +125,15 @@ def add_cert( :type pin: str, optional :param private: Whether the object is a private key. Defaults to False. :type private: bool, optional + :param token_label: Label of the target token. When ``None`` (the + default) ``pkcs11-tool`` writes to the first available token. + Set this when multiple tokens exist to target a specific one. + :type token_label: str | None, optional + :param label: Label for the PKCS#11 object being written. Required + when ``p11_child`` accesses the token directly (i.e. without + ``virt_cacard``), because the response parser expects a + non-empty label. + :type label: str | None, optional """ obj_type = "privkey" if private else "cert" args: CLIBuilderArgs = { @@ -104,9 +144,20 @@ def add_cert( "type": (self.cli.option.VALUE, obj_type), "id": (self.cli.option.VALUE, cert_id), } + if token_label is not None: + args["token-label"] = (self.cli.option.VALUE, token_label) + if label is not None: + args["label"] = (self.cli.option.VALUE, label) self.host.conn.run(self.cli.command("pkcs11-tool", args), env={"SOFTHSM2_CONF": self.SOFTHSM2_CONF_PATH}) - def add_key(self, key_path: str, key_id: str = "01", pin: str = "123456") -> None: + def add_key( + self, + key_path: str, + key_id: str = "01", + pin: str = "123456", + token_label: str | None = None, + label: str | None = None, + ) -> None: """ Adds a private key to the smart card. @@ -116,8 +167,12 @@ def add_key(self, key_path: str, key_id: str = "01", pin: str = "123456") -> Non :type key_id: str, optional :param pin: User PIN, defaults to "123456" :type pin: str, optional + :param token_label: Label of the target token (see :meth:`add_cert`). + :type token_label: str | None, optional + :param label: Label for the PKCS#11 object (see :meth:`add_cert`). + :type label: str | None, optional """ - self.add_cert(cert_path=key_path, cert_id=key_id, pin=pin, private=True) + self.add_cert(cert_path=key_path, cert_id=key_id, pin=pin, private=True, token_label=token_label, label=label) def generate_cert( self, @@ -150,6 +205,40 @@ def generate_cert( self.host.conn.run(self.cli.command("openssl req", args)) return key_path, cert_path + def register_for_p11_child(self) -> None: + """ + Register SoftHSM as a system-wide p11-kit module visible to ``p11_child``. + + This is required for multi-token scenarios where ``p11_child`` must + iterate all PKCS#11 slots. The method: + + 1. Ensures ``slots.removable = true`` is set in the SoftHSM config. + 2. Copies the SoftHSM config to ``/etc/softhsm2.conf`` so that + ``p11_child`` (running as the *sssd* user) can find it without + ``SOFTHSM2_CONF`` in its environment. + 3. Registers the SoftHSM module with p11-kit. + 4. Creates a systemd drop-in for ``sssd.service`` that exports + ``SOFTHSM2_CONF``. + 5. Adjusts file permissions so the *sssd* user can access the token + storage directory. + """ + conf = self.SOFTHSM2_CONF_PATH + module = "/usr/lib64/pkcs11/libsofthsm2.so" + + self.host.conn.run(f"grep -q 'slots.removable' {conf} || echo 'slots.removable = true' >> {conf}") + self.host.conn.run(f"cp {conf} /etc/softhsm2.conf") + self.host.conn.run(f'echo "module: {module}" > /etc/pkcs11/modules/softhsm2.module') + self.fs.mkdir_p("/etc/systemd/system/sssd.service.d") + self.host.conn.run( + f'printf "[Service]\\nEnvironment=SOFTHSM2_CONF={conf}\\n" ' + f"> /etc/systemd/system/sssd.service.d/softhsm.conf" + ) + self.host.conn.run("systemctl daemon-reload") + self.host.conn.run("chmod -R o+rX /opt/test_ca/") + self.host.conn.run( + f"chown -R sssd:sssd {self.TOKEN_STORAGE_PATH}/ " f"&& chmod -R 770 {self.TOKEN_STORAGE_PATH}/" + ) + def insert_card(self) -> None: """ Simulates card insertion by starting the smart card service.