Skip to content

Commit 9672c6a

Browse files
krishnavemaspoore1
authored andcommitted
Add smartcard utils
1 parent e9fd941 commit 9672c6a

File tree

2 files changed

+200
-0
lines changed

2 files changed

+200
-0
lines changed

sssd_test_framework/roles/client.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from ..utils.local_users import LocalUsersUtils
1313
from ..utils.realmd import RealmUtils
1414
from ..utils.sbus import DBUSDestination, DBUSKnownBus
15+
from ..utils.smartcard import SmartCardUtils
1516
from ..utils.sss_override import SSSOverrideUtils
1617
from ..utils.sssctl import SSSCTLUtils
1718
from ..utils.sssd import SSSDUtils
@@ -92,6 +93,11 @@ def __init__(self, *args, **kwargs) -> None:
9293
The D-bus destination for infopipe.
9394
"""
9495

96+
self.smartcard: SmartCardUtils = SmartCardUtils(self.host, self.fs, self.svc)
97+
"""
98+
Utility class for managing smart card operations using SoftHSM and PKCS#11.
99+
"""
100+
95101
def setup(self) -> None:
96102
"""
97103
Called before execution of each test.
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
from pytest_mh import MultihostHost, MultihostUtility
6+
from pytest_mh.cli import CLIBuilder, CLIBuilderArgs
7+
from pytest_mh.utils.fs import LinuxFileSystem
8+
from pytest_mh.utils.services import SystemdServices
9+
10+
if TYPE_CHECKING:
11+
from ..roles.client import Client
12+
13+
__all__ = [
14+
"SmartCardUtils",
15+
]
16+
17+
18+
class SmartCardUtils(MultihostUtility[MultihostHost]):
19+
"""
20+
Utility class for managing smart card operations using SoftHSM and PKCS#11.
21+
"""
22+
23+
SOFTHSM2_CONF_PATH = "/opt/test_ca/softhsm2.conf"
24+
TOKEN_STORAGE_PATH = "/opt/test_ca/tokens"
25+
OPENSC_CACHE_PATHS = [
26+
"$HOME/.cache/opensc/",
27+
"/run/sssd/.cache/opensc/",
28+
]
29+
30+
def __init__(self, host: MultihostHost, fs: LinuxFileSystem, svc: SystemdServices) -> None:
31+
"""
32+
:param host: Multihost object.
33+
:type host: MultihostHost
34+
:param fs: Filesystem utility object.
35+
:type fs: LinuxFileSystem
36+
:param svc: Systemd svc utility object.
37+
:type svc: SystemdServices
38+
"""
39+
super().__init__(host)
40+
41+
self.cli: CLIBuilder = host.cli
42+
"""CLI builder utility for command construction."""
43+
44+
self.fs: LinuxFileSystem = fs
45+
"""Filesystem utility used to handle file operations."""
46+
47+
self.svc: SystemdServices = svc
48+
"""Systemd utility to manage and interact with svc."""
49+
50+
def initialize_card(self, label: str = "sc_test", so_pin: str = "12345678", user_pin: str = "123456") -> None:
51+
"""
52+
Initializes a SoftHSM token with the given label and PINs.
53+
54+
Cleans cache directories and prepares the token directory.
55+
56+
:param label: Token label, defaults to "sc_test"
57+
:type label: str, optional
58+
:param so_pin: Security Officer PIN, defaults to "12345678"
59+
:type so_pin: str, optional
60+
:param user_pin: User PIN, defaults to "123456"
61+
:type user_pin: str, optional
62+
"""
63+
for path in self.OPENSC_CACHE_PATHS:
64+
self.fs.rm(path)
65+
66+
self.fs.rm(self.TOKEN_STORAGE_PATH)
67+
self.fs.mkdir_p(self.TOKEN_STORAGE_PATH)
68+
69+
args: CLIBuilderArgs = {
70+
"label": (self.cli.option.VALUE, label),
71+
"free": (self.cli.option.SWITCH, True),
72+
"so-pin": (self.cli.option.VALUE, so_pin),
73+
"pin": (self.cli.option.VALUE, user_pin),
74+
}
75+
self.host.conn.run(
76+
self.cli.command("softhsm2-util --init-token", args), env={"SOFTHSM2_CONF": self.SOFTHSM2_CONF_PATH}
77+
)
78+
79+
def add_cert(
80+
self,
81+
cert_path: str,
82+
cert_id: str = "01",
83+
pin: str = "123456",
84+
private: bool | None = False,
85+
) -> None:
86+
"""
87+
Adds a certificate or private key to the smart card.
88+
89+
:param cert_path: Path to the certificate or key file.
90+
:type cert_path: str
91+
:param cert_id: Object ID, defaults to "01"
92+
:type cert_id: str, optional
93+
:param pin: User PIN, defaults to "123456"
94+
:type pin: str, optional
95+
:param private: Whether the object is a private key. Defaults to False.
96+
:type private: bool, optional
97+
"""
98+
obj_type = "privkey" if private else "cert"
99+
args: CLIBuilderArgs = {
100+
"module": (self.cli.option.VALUE, "/usr/lib64/pkcs11/libsofthsm2.so"),
101+
"login": (self.cli.option.SWITCH, True),
102+
"pin": (self.cli.option.VALUE, pin),
103+
"write-object": (self.cli.option.VALUE, cert_path),
104+
"type": (self.cli.option.VALUE, obj_type),
105+
"id": (self.cli.option.VALUE, cert_id),
106+
}
107+
self.host.conn.run(self.cli.command("pkcs11-tool", args), env={"SOFTHSM2_CONF": self.SOFTHSM2_CONF_PATH})
108+
109+
def add_key(self, key_path: str, key_id: str = "01", pin: str = "123456") -> None:
110+
"""
111+
Adds a private key to the smart card.
112+
113+
:param key_path: Path to the private key.
114+
:type key_path: str
115+
:param key_id: Key ID, defaults to "01"
116+
:type key_id: str, optional
117+
:param pin: User PIN, defaults to "123456"
118+
:type pin: str, optional
119+
"""
120+
self.add_cert(cert_path=key_path, cert_id=key_id, pin=pin, private=True)
121+
122+
def generate_cert(
123+
self,
124+
key_path: str = "/tmp/selfsigned.key",
125+
cert_path: str = "/tmp/selfsigned.crt",
126+
subj: str = "/CN=Test Cert",
127+
) -> tuple[str, str]:
128+
"""
129+
Generates a self-signed certificate and private key.
130+
131+
:param key_path: Output path for the private key, defaults to "/tmp/selfsigned.key"
132+
:type key_path: str, optional
133+
:param cert_path: Output path for the certificate, defaults to "/tmp/selfsigned.crt"
134+
:type cert_path: str, optional
135+
:param subj: Subject for the certificate, defaults to "/CN=Test Cert"
136+
:type subj: str, optional
137+
:return: Tuple of (key_path, cert_path)
138+
:rtype: tuple
139+
"""
140+
args: CLIBuilderArgs = {
141+
"x509": (self.cli.option.SWITCH, True),
142+
"nodes": (self.cli.option.SWITCH, True),
143+
"sha256": (self.cli.option.SWITCH, True),
144+
"days": (self.cli.option.VALUE, "365"),
145+
"newkey": (self.cli.option.VALUE, "rsa:2048"),
146+
"keyout": (self.cli.option.VALUE, key_path),
147+
"out": (self.cli.option.VALUE, cert_path),
148+
"subj": (self.cli.option.VALUE, subj),
149+
}
150+
self.host.conn.run(self.cli.command("openssl req", args))
151+
return key_path, cert_path
152+
153+
def insert_card(self) -> None:
154+
"""
155+
Simulates card insertion by starting the smart card service.
156+
"""
157+
self.svc.start("virt_cacard.service")
158+
159+
def remove_card(self) -> None:
160+
"""
161+
Simulates card removal by stopping the smart card service.
162+
"""
163+
self.svc.stop("virt_cacard.service")
164+
165+
def setup_local_card(self, client: Client, username: str) -> None:
166+
"""
167+
Setup local system for smart card authentication.
168+
169+
.. code-block:: python
170+
:caption: Example usage
171+
172+
@pytest.mark.topology(KnownTopology.Client)
173+
def test_example(client: Client):
174+
client.smartcard.setup_local_card(client, 'localuser1')
175+
176+
result = client.host.conn.run("su - localuser1 -c 'su - localuser1 -c whoami'", input="123456")
177+
assert "PIN" in result.stderr
178+
assert "localuser1" in result.stdout
179+
"""
180+
client.host.fs.rm("/etc/sssd/pki/sssd_auth_ca_db.pem")
181+
key, cert = self.generate_cert()
182+
self.initialize_card()
183+
self.add_key(key)
184+
self.add_cert(cert)
185+
client.authselect.select("sssd", ["with-smartcard"])
186+
self.svc.restart("virt_cacard.service")
187+
client.sssd.common.local()
188+
client.sssd.dom("local")["local_auth_policy"] = "only"
189+
client.sssd.section(f"certmap/local/{username}")["matchrule"] = "<SUBJECT>.*CN=Test Cert.*"
190+
client.sssd.pam["pam_cert_auth"] = "True"
191+
192+
data = client.host.fs.read(cert)
193+
client.host.fs.append("/etc/sssd/pki/sssd_auth_ca_db.pem", data)
194+
client.sssd.start()

0 commit comments

Comments
 (0)