Skip to content
Draft
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
93 changes: 91 additions & 2 deletions sssd_test_framework/utils/smartcard.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Copy link
Contributor

Choose a reason for hiding this comment

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

Could the initialize_card() method be modified to take an option to skip the cleanup if set instead of having an additional method?

"""
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}
)
Comment on lines +98 to +106

Choose a reason for hiding this comment

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

medium

The logic for building arguments and calling softhsm2-util --init-token in this new method is identical to the one in the initialize_card method (lines 69-77). This code duplication should be avoided by extracting the common logic into a private helper method. This will improve code maintainability.


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.
Expand All @@ -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 = {
Expand All @@ -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.

Expand All @@ -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,
Expand Down Expand Up @@ -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:
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm thinking this should move to the SSSDCommonConfiguration class along with the settings from SSSD/sssd#8519 for configure_sssd_for_smartcard(). You could create a smartcard_with_softhsm() method that wraps all of that together into a single method you could call from the test with something like:
client.sssd.common.smartcard_with_softhsm()

"""
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/")

Choose a reason for hiding this comment

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

medium

The command chmod -R o+rX /opt/test_ca/ grants read permissions to all files under /opt/test_ca/ to all users on the system, which is overly permissive. While this is a test environment, it's better to apply the principle of least privilege. A more precise command would only grant the necessary permissions, for example by only allowing traversal of the directory and read access to the configuration file.

Suggested change
self.host.conn.run("chmod -R o+rX /opt/test_ca/")
self.host.conn.run(f"chmod o+x /opt/test_ca && chmod o+r {conf}")

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.
Expand Down
Loading