SCAutolib 1.1.0__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of SCAutolib might be problematic. Click here for more details.
- SCAutolib/__init__.py +20 -21
- SCAutolib/cli_commands.py +2 -1
- SCAutolib/controller.py +168 -106
- SCAutolib/enums.py +40 -0
- SCAutolib/models/CA.py +111 -39
- SCAutolib/models/card.py +222 -62
- SCAutolib/models/file.py +15 -12
- SCAutolib/models/gui.py +2 -1
- SCAutolib/models/user.py +27 -194
- SCAutolib/templates/user.cnf +1 -1
- SCAutolib/utils.py +73 -50
- {SCAutolib-1.1.0.dist-info → SCAutolib-3.0.0.dist-info}/METADATA +10 -10
- SCAutolib-3.0.0.dist-info/RECORD +27 -0
- {SCAutolib-1.1.0.dist-info → SCAutolib-3.0.0.dist-info}/WHEEL +1 -1
- SCAutolib-1.1.0.dist-info/RECORD +0 -26
- {SCAutolib-1.1.0.dist-info → SCAutolib-3.0.0.dist-info}/LICENSE +0 -0
- {SCAutolib-1.1.0.dist-info → SCAutolib-3.0.0.dist-info}/entry_points.txt +0 -0
- {SCAutolib-1.1.0.dist-info → SCAutolib-3.0.0.dist-info}/top_level.txt +0 -0
SCAutolib/models/CA.py
CHANGED
|
@@ -19,12 +19,15 @@ from socket import gethostname
|
|
|
19
19
|
from SCAutolib import TEMPLATES_DIR, logger, run, LIB_DIR, LIB_DUMP_CAS
|
|
20
20
|
from SCAutolib.exceptions import SCAutolibException
|
|
21
21
|
from SCAutolib.models.file import OpensslCnf
|
|
22
|
+
from SCAutolib.enums import CAType
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class BaseCA:
|
|
25
26
|
dump_file: Path = None
|
|
27
|
+
ca_type: str = None
|
|
26
28
|
_ca_cert: Path = None
|
|
27
29
|
_ca_key: Path = None
|
|
30
|
+
_ca_pki_db: Path = Path("/etc/sssd/pki/sssd_auth_ca_db.pem")
|
|
28
31
|
|
|
29
32
|
@property
|
|
30
33
|
def cert(self):
|
|
@@ -49,13 +52,35 @@ class BaseCA:
|
|
|
49
52
|
def setup(self):
|
|
50
53
|
"""
|
|
51
54
|
Configure the CA
|
|
52
|
-
|
|
53
|
-
:param force: In case if CA is already configured, specifies if it
|
|
54
|
-
should be reconfigured with force
|
|
55
|
-
:type force: bool
|
|
56
55
|
"""
|
|
57
56
|
...
|
|
58
57
|
|
|
58
|
+
def update_ca_db(self):
|
|
59
|
+
"""
|
|
60
|
+
Update /etc/sssd/pki/sssd_auth_ca_db.pem with certificate defined in CA
|
|
61
|
+
object.
|
|
62
|
+
"""
|
|
63
|
+
with self._ca_cert.open("r") as f_cert:
|
|
64
|
+
root_cert = f_cert.read()
|
|
65
|
+
|
|
66
|
+
if self._ca_pki_db.exists():
|
|
67
|
+
# Check if current CA cert is already present in the sssd auth db
|
|
68
|
+
with self._ca_pki_db.open("a+") as f:
|
|
69
|
+
f.seek(0)
|
|
70
|
+
data = f.read()
|
|
71
|
+
if root_cert not in data:
|
|
72
|
+
f.write(root_cert)
|
|
73
|
+
else:
|
|
74
|
+
# Create /etc/sssd/pki directory if it doesn't exist
|
|
75
|
+
self._ca_pki_db.parents[0].mkdir(exist_ok=True)
|
|
76
|
+
with self._ca_pki_db.open("w") as f:
|
|
77
|
+
f.write(root_cert)
|
|
78
|
+
logger.debug(
|
|
79
|
+
f"CA certificate {self._ca_cert} is copied to {self._ca_pki_db}")
|
|
80
|
+
# Restoring SELinux context on the sssd auth db
|
|
81
|
+
run(f"restorecon -v {self._ca_pki_db}")
|
|
82
|
+
logger.info("Local CA is updated")
|
|
83
|
+
|
|
59
84
|
def sign_cert(self):
|
|
60
85
|
"""
|
|
61
86
|
Sign the certificate
|
|
@@ -80,7 +105,7 @@ class BaseCA:
|
|
|
80
105
|
with json_file.open("r") as f:
|
|
81
106
|
cnt = json.load(f)
|
|
82
107
|
|
|
83
|
-
if "
|
|
108
|
+
if cnt["ca_type"] == CAType.ipa:
|
|
84
109
|
ca = IPAServerCA(ip_addr=cnt["_ipa_server_ip"],
|
|
85
110
|
server_hostname=cnt["_ipa_server_hostname"],
|
|
86
111
|
root_passwd=cnt["_ipa_server_root_passwd"],
|
|
@@ -88,15 +113,27 @@ class BaseCA:
|
|
|
88
113
|
client_hostname=cnt["_ipa_client_hostname"],
|
|
89
114
|
domain=cnt["_ipa_server_domain"],
|
|
90
115
|
realm=cnt["_ipa_server_realm"])
|
|
91
|
-
|
|
116
|
+
elif cnt["ca_type"] == CAType.custom:
|
|
117
|
+
ca = CustomCA(cnt)
|
|
118
|
+
elif cnt["ca_type"] == CAType.local:
|
|
92
119
|
ca = LocalCA(root_dir=cnt["root_dir"])
|
|
93
|
-
|
|
120
|
+
else:
|
|
121
|
+
raise SCAutolibException("CA object has unknown type. Only ipa, "
|
|
122
|
+
"custom and local types are supported. CA "
|
|
123
|
+
"object not loaded")
|
|
124
|
+
|
|
125
|
+
logger.debug(f"CA {cnt['name']} is loaded from file {json_file}")
|
|
94
126
|
return ca
|
|
95
127
|
|
|
96
128
|
|
|
97
129
|
class LocalCA(BaseCA):
|
|
130
|
+
"""
|
|
131
|
+
Represents local CA that is created as CA for virtual cards.
|
|
132
|
+
"""
|
|
98
133
|
template = Path(TEMPLATES_DIR, "ca.cnf")
|
|
99
|
-
|
|
134
|
+
ca_type = CAType.local
|
|
135
|
+
ca_name = "local_ca"
|
|
136
|
+
dump_file = LIB_DUMP_CAS.joinpath(f"{ca_name}.json")
|
|
100
137
|
|
|
101
138
|
def __init__(self, root_dir: Path = None, cnf: OpensslCnf = None):
|
|
102
139
|
"""
|
|
@@ -106,15 +143,20 @@ class LocalCA(BaseCA):
|
|
|
106
143
|
:param root_dir: Path to root directory of the CA. By default, is in
|
|
107
144
|
/etc/SCAutolib/ca
|
|
108
145
|
:type: Path
|
|
146
|
+
:param cnf: object representing openssl cnf file
|
|
147
|
+
:type cnf: OpensslCnf
|
|
109
148
|
"""
|
|
149
|
+
self.name = LocalCA.ca_name
|
|
150
|
+
self.ca_type = LocalCA.ca_type
|
|
110
151
|
self.root_dir: Path = Path("/etc/SCAutolib/ca") if root_dir is None \
|
|
111
|
-
else root_dir
|
|
112
|
-
|
|
152
|
+
else Path(root_dir)
|
|
153
|
+
if not self.root_dir.exists():
|
|
154
|
+
raise FileNotFoundError("Root directory of CA does not exist.")
|
|
113
155
|
self._conf_dir: Path = self.root_dir.joinpath("conf")
|
|
114
156
|
self._newcerts: Path = self.root_dir.joinpath("newcerts")
|
|
115
157
|
self._certs: Path = self.root_dir.joinpath("certs")
|
|
116
158
|
self._crl: Path = self.root_dir.joinpath("crl", "root.pem")
|
|
117
|
-
self._ca_pki_db
|
|
159
|
+
self._ca_pki_db = BaseCA._ca_pki_db
|
|
118
160
|
|
|
119
161
|
self._ca_cnf: OpensslCnf = cnf if cnf else OpensslCnf(
|
|
120
162
|
conf_type="CA",
|
|
@@ -177,10 +219,7 @@ class LocalCA(BaseCA):
|
|
|
177
219
|
|
|
178
220
|
def setup(self):
|
|
179
221
|
"""
|
|
180
|
-
Creates directory and file structure needed by local CA.
|
|
181
|
-
already exists and force = True, directory would be recursively deleted
|
|
182
|
-
and new local CA would be created. Otherwise, configuration would be
|
|
183
|
-
skipped.
|
|
222
|
+
Creates directory and file structure needed by local CA.
|
|
184
223
|
"""
|
|
185
224
|
if self._ca_cnf is None:
|
|
186
225
|
raise SCAutolibException("CA CNF file is not set")
|
|
@@ -211,31 +250,12 @@ class LocalCA(BaseCA):
|
|
|
211
250
|
run(['openssl', 'ca', '-config', self._ca_cnf.path, '-gencrl',
|
|
212
251
|
'-out', self._crl], check=True)
|
|
213
252
|
|
|
214
|
-
|
|
215
|
-
root_cert = f_cert.read()
|
|
216
|
-
|
|
217
|
-
if self._ca_pki_db.exists():
|
|
218
|
-
# Check if current CA cert doesn't present in the sssd auth db
|
|
219
|
-
with self._ca_pki_db.open("a+") as f:
|
|
220
|
-
data = f.read()
|
|
221
|
-
if root_cert not in data:
|
|
222
|
-
f.write(root_cert)
|
|
223
|
-
else:
|
|
224
|
-
# Create /etc/sssd/pki directory if it doesn't exist
|
|
225
|
-
self._ca_pki_db.parents[0].mkdir(exist_ok=True)
|
|
226
|
-
with self._ca_pki_db.open("w") as f:
|
|
227
|
-
f.write(root_cert)
|
|
228
|
-
logger.debug(
|
|
229
|
-
f"CA certificate {self._ca_cert} is copied to {self._ca_pki_db}")
|
|
230
|
-
# Restoring SELinux context on the sssd auth db
|
|
231
|
-
run(f"restorecon -v {self._ca_pki_db}")
|
|
232
|
-
|
|
233
|
-
logger.info("Local CA is configured")
|
|
253
|
+
logger.info("Local CA files are prepared")
|
|
234
254
|
|
|
235
255
|
def request_cert(self, csr: Path, username: str,
|
|
236
256
|
cert_out: Path = None) -> Path:
|
|
237
257
|
"""
|
|
238
|
-
Create the certificate from CSR and sign it. Certificate is
|
|
258
|
+
Create the certificate from CSR and sign it. Certificate is stored
|
|
239
259
|
in the <root ca directory>/ca/newcerts directory with name username.pem
|
|
240
260
|
|
|
241
261
|
:param csr: path to CSR
|
|
@@ -292,6 +312,53 @@ class LocalCA(BaseCA):
|
|
|
292
312
|
logger.info(f"Local CA from {self.root_dir} is removed")
|
|
293
313
|
|
|
294
314
|
|
|
315
|
+
class CustomCA(BaseCA):
|
|
316
|
+
"""
|
|
317
|
+
:TODO: CustomCA is not tested yet and it's not functional until physical
|
|
318
|
+
cards testing with removinator is implemented
|
|
319
|
+
Represents CA for physical cards. Physical cards are often read-only and
|
|
320
|
+
rootCA certs or bundles are provided with a card. This class provides
|
|
321
|
+
methods for manipulation with rootCA certs of physical cards.
|
|
322
|
+
"""
|
|
323
|
+
ca_type = CAType.custom
|
|
324
|
+
|
|
325
|
+
def __init__(self, card: dict):
|
|
326
|
+
"""
|
|
327
|
+
Initialize required attributes
|
|
328
|
+
"""
|
|
329
|
+
self.ca_type = CustomCA.ca_type
|
|
330
|
+
self.name = card["ca_name"]
|
|
331
|
+
self.ca_cert = card["ca_cert"]
|
|
332
|
+
self.dump_file = LIB_DUMP_CAS.joinpath(f"{self.name}.json")
|
|
333
|
+
self.root_dir: Path = LIB_DIR.joinpath(self.name)
|
|
334
|
+
self._ca_cert = self.root_dir.joinpath(f"{self.name}.pem")
|
|
335
|
+
self._ca_pki_db: Path = BaseCA._ca_pki_db
|
|
336
|
+
|
|
337
|
+
def setup(self):
|
|
338
|
+
"""
|
|
339
|
+
Create rootCA file. Actually, copy cert from conf.json
|
|
340
|
+
"""
|
|
341
|
+
self.root_dir.mkdir(parents=True, exist_ok=True)
|
|
342
|
+
if self.ca_cert is None:
|
|
343
|
+
raise SCAutolibException(
|
|
344
|
+
f"CA cerf for {self.name} not found")
|
|
345
|
+
with self._ca_cert.open('w') as newcert:
|
|
346
|
+
newcert.write(self.ca_cert)
|
|
347
|
+
logger.info("Local CA files are prepared")
|
|
348
|
+
|
|
349
|
+
def to_dict(self):
|
|
350
|
+
"""
|
|
351
|
+
Customising default property for better serialisation for storing to
|
|
352
|
+
JSON format.
|
|
353
|
+
|
|
354
|
+
:return: dictionary with all values. Path objects are typed to string.
|
|
355
|
+
:rtype: dict
|
|
356
|
+
"""
|
|
357
|
+
dict_ = {k: str(v) if type(v) is PosixPath else v
|
|
358
|
+
for k, v in super().__dict__.items()}
|
|
359
|
+
return dict_
|
|
360
|
+
|
|
361
|
+
|
|
295
362
|
class IPAServerCA(BaseCA):
|
|
296
363
|
"""
|
|
297
364
|
Class represents IPA server with integrated CA. Through this class
|
|
@@ -304,7 +371,8 @@ class IPAServerCA(BaseCA):
|
|
|
304
371
|
connection is made to the server and the script is fetched in frame of
|
|
305
372
|
``IPAServerCA.create()`` method.
|
|
306
373
|
"""
|
|
307
|
-
|
|
374
|
+
ca_type = CAType.ipa
|
|
375
|
+
ca_name = "IPA"
|
|
308
376
|
_ca_cert: Path = Path("/etc/ipa/ca.crt")
|
|
309
377
|
_ipa_server_ip: str = None
|
|
310
378
|
_ipa_server_hostname: str = None
|
|
@@ -343,6 +411,8 @@ class IPAServerCA(BaseCA):
|
|
|
343
411
|
be used instead
|
|
344
412
|
:type realm: str
|
|
345
413
|
"""
|
|
414
|
+
self.ca_type = IPAServerCA.ca_type
|
|
415
|
+
self.name = IPAServerCA.ca_name
|
|
346
416
|
self._ipa_server_ip = ip_addr
|
|
347
417
|
self._ipa_server_hostname = server_hostname
|
|
348
418
|
self._add_to_hosts() # So we can log in to the IPA before setup
|
|
@@ -438,7 +508,8 @@ class IPAServerCA(BaseCA):
|
|
|
438
508
|
if "365" not in policy["krbmaxpwdlife"]:
|
|
439
509
|
self.meta_client.pwpolicy_mod(a_cn="global_policy",
|
|
440
510
|
o_krbmaxpwdlife=365)
|
|
441
|
-
logger.debug(
|
|
511
|
+
logger.debug(
|
|
512
|
+
"Maximum kerberos password lifetime is set to 365 days")
|
|
442
513
|
|
|
443
514
|
# TODO: add to restore client host name
|
|
444
515
|
logger.info("IPA client is configured on the system.")
|
|
@@ -543,7 +614,8 @@ class IPAServerCA(BaseCA):
|
|
|
543
614
|
# in_stream = False is required because while testing with pytest
|
|
544
615
|
# it collision appears with capturing of the output.
|
|
545
616
|
logger.debug("Running kinit on the IPA server")
|
|
546
|
-
c.run("kinit admin", pty=True,
|
|
617
|
+
c.run("kinit admin", pty=True,
|
|
618
|
+
watchers=[kinitpass], in_stream=False)
|
|
547
619
|
result = c.run("ipa-advise config-client-for-smart-card-auth",
|
|
548
620
|
hide=True, in_stream=False)
|
|
549
621
|
logger.debug("Script is generated on server side")
|
SCAutolib/models/card.py
CHANGED
|
@@ -11,6 +11,7 @@ from traceback import format_exc
|
|
|
11
11
|
|
|
12
12
|
from SCAutolib import run, logger, TEMPLATES_DIR, LIB_DUMP_CARDS
|
|
13
13
|
from SCAutolib.exceptions import SCAutolibException
|
|
14
|
+
from SCAutolib.enums import CardType, UserType
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
class Card:
|
|
@@ -21,22 +22,27 @@ class Card:
|
|
|
21
22
|
uri: str = None
|
|
22
23
|
dump_file: Path = None
|
|
23
24
|
type: str = None
|
|
24
|
-
_user = None
|
|
25
25
|
_pattern: str = None
|
|
26
26
|
|
|
27
27
|
def _set_uri(self):
|
|
28
28
|
"""
|
|
29
29
|
Sets URI for given smart card. Uri is matched from ``p11tool`` command
|
|
30
|
-
with regular expression. If URI is not matched,
|
|
31
|
-
would be raised
|
|
30
|
+
with regular expression. If URI is not matched, exception is raised.
|
|
32
31
|
|
|
33
|
-
:raise:
|
|
32
|
+
:raise: SCAutolibException
|
|
34
33
|
"""
|
|
35
34
|
cmd = ["p11tool", "--list-token-urls"]
|
|
36
35
|
out = run(cmd).stdout
|
|
37
36
|
urls = re.findall(self._pattern, out)
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
if len(urls) == 1:
|
|
38
|
+
self.uri = urls[0]
|
|
39
|
+
logger.info(f"Card URI is set to {self.uri}")
|
|
40
|
+
elif len(urls) == 0:
|
|
41
|
+
logger.warning("URI not set")
|
|
42
|
+
raise SCAutolibException("URI matching expected pattern not found.")
|
|
43
|
+
else:
|
|
44
|
+
logger.warning("Multiple matching URIs found. URI not set")
|
|
45
|
+
raise SCAutolibException("Multiple URIs match expected pattern.")
|
|
40
46
|
|
|
41
47
|
def insert(self):
|
|
42
48
|
"""
|
|
@@ -57,26 +63,25 @@ class Card:
|
|
|
57
63
|
...
|
|
58
64
|
|
|
59
65
|
@staticmethod
|
|
60
|
-
def load(json_file
|
|
66
|
+
def load(json_file):
|
|
61
67
|
with json_file.open("r") as f:
|
|
62
68
|
cnt = json.load(f)
|
|
63
69
|
|
|
64
70
|
card = None
|
|
65
|
-
if cnt["
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
card.uri = cnt["uri"]
|
|
71
|
+
if cnt["card_type"] == CardType.virtual:
|
|
72
|
+
card = VirtualCard(cnt, softhsm2_conf=Path(cnt["softhsm"]))
|
|
73
|
+
# card.uri = cnt["uri"]
|
|
74
|
+
elif cnt["card_type"] == CardType.physical:
|
|
75
|
+
card = PhysicalCard(cnt)
|
|
71
76
|
else:
|
|
72
77
|
raise SCAutolibException(
|
|
73
|
-
f"Unknown card type: {cnt['
|
|
78
|
+
f"Unknown card type: {cnt['card_type']}")
|
|
74
79
|
return card
|
|
75
80
|
|
|
76
81
|
|
|
77
82
|
class VirtualCard(Card):
|
|
78
83
|
"""
|
|
79
|
-
This class provides
|
|
84
|
+
This class provides methods for operations on virtual smart card. Virtual
|
|
80
85
|
smart card by itself is represented by the systemd service in the system.
|
|
81
86
|
The card relates to some user, so providing the user is essential for
|
|
82
87
|
correct functioning of methods for the virtual smart card.
|
|
@@ -92,34 +97,60 @@ class VirtualCard(Card):
|
|
|
92
97
|
_pattern = r"(pkcs11:model=PKCS%2315%20emulated;" \
|
|
93
98
|
r"manufacturer=Common%20Access%20Card;serial=.*)"
|
|
94
99
|
_inserted: bool = False
|
|
95
|
-
type = "virtual"
|
|
96
100
|
|
|
97
|
-
|
|
101
|
+
name: str = None
|
|
102
|
+
pin: str = None
|
|
103
|
+
cardholder: str = None
|
|
104
|
+
CN: str = None
|
|
105
|
+
UID: str = None
|
|
106
|
+
key: Path = None
|
|
107
|
+
cert: Path = None
|
|
108
|
+
card_dir: Path = None
|
|
109
|
+
card_type: str = None
|
|
110
|
+
ca_name: str = None
|
|
111
|
+
slot: str = None
|
|
112
|
+
user = None
|
|
113
|
+
cnf = None
|
|
114
|
+
|
|
115
|
+
def __init__(self, card_data, softhsm2_conf: Path = None,
|
|
116
|
+
card_dir: Path = None, key: Path = None, cert: Path = None):
|
|
98
117
|
"""
|
|
99
118
|
Initialise virtual smart card. Constructor of the base class is also
|
|
100
119
|
used.
|
|
101
120
|
|
|
102
|
-
:param
|
|
103
|
-
:type
|
|
121
|
+
:param card_data: dict containing card details as pin, cardholder etc.
|
|
122
|
+
:type card_data: dict
|
|
104
123
|
:param softhsm2_conf: path to SoftHSM2 configuration file
|
|
105
124
|
:type softhsm2_conf: pathlib.Path
|
|
125
|
+
:param card_dir: path to card directory where card files will be saved
|
|
126
|
+
:type card_dir: pathlib.Path
|
|
127
|
+
:param key: path to key - if the key exist it will be used with the card
|
|
128
|
+
:type key: pathlib.Path
|
|
129
|
+
:param cert: path to certificate. If file exist it will be used with the
|
|
130
|
+
card
|
|
131
|
+
:type cert: pathlib.Path
|
|
106
132
|
"""
|
|
107
|
-
|
|
108
|
-
self.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
self.
|
|
112
|
-
|
|
113
|
-
self.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
133
|
+
self.name = card_data["name"]
|
|
134
|
+
self.pin = card_data["pin"]
|
|
135
|
+
self.cardholder = card_data["cardholder"]
|
|
136
|
+
self.card_type = card_data["card_type"]
|
|
137
|
+
self.CN = card_data["CN"]
|
|
138
|
+
self.ca_name = card_data["ca_name"]
|
|
139
|
+
self.card_dir = card_dir if card_dir is not None \
|
|
140
|
+
else Path(card_data["card_dir"])
|
|
141
|
+
if not self.card_dir.exists():
|
|
142
|
+
raise FileNotFoundError("Card root directory doesn't exists")
|
|
143
|
+
self.dump_file = LIB_DUMP_CARDS.joinpath(f"{self.name}.json")
|
|
144
|
+
self.key = key \
|
|
145
|
+
if key else self.card_dir.joinpath(f"key-{self.name}.pem")
|
|
146
|
+
self.cert = cert \
|
|
147
|
+
if cert else self.card_dir.joinpath(f"cert-{self.name}.pem")
|
|
148
|
+
self._service_name = self.name
|
|
117
149
|
self._service_location = Path(
|
|
118
150
|
f"/etc/systemd/system/{self._service_name}.service")
|
|
119
|
-
self.
|
|
120
|
-
f"card-{self._user.username}.json")
|
|
151
|
+
self._nssdb = self.card_dir.joinpath("db")
|
|
121
152
|
self._softhsm2_conf = softhsm2_conf if softhsm2_conf \
|
|
122
|
-
else Path(
|
|
153
|
+
else Path(self.card_dir, "softhsm2.conf")
|
|
123
154
|
|
|
124
155
|
def __call__(self, insert: bool):
|
|
125
156
|
"""
|
|
@@ -140,8 +171,8 @@ class VirtualCard(Card):
|
|
|
140
171
|
|
|
141
172
|
:return: self
|
|
142
173
|
"""
|
|
143
|
-
|
|
144
|
-
"Service for virtual sc doesn't exists."
|
|
174
|
+
if not self._service_location.exists():
|
|
175
|
+
raise FileNotFoundError("Service for virtual sc doesn't exists.")
|
|
145
176
|
return self
|
|
146
177
|
|
|
147
178
|
def __exit__(self, exp_type, exp_value, exp_traceback):
|
|
@@ -161,10 +192,18 @@ class VirtualCard(Card):
|
|
|
161
192
|
|
|
162
193
|
def to_dict(self):
|
|
163
194
|
return {
|
|
164
|
-
"
|
|
165
|
-
"
|
|
195
|
+
"name": self.name,
|
|
196
|
+
"pin": self.pin,
|
|
197
|
+
"cardholder": self.cardholder,
|
|
198
|
+
"card_type": self.card_type,
|
|
199
|
+
"CN": self.CN,
|
|
200
|
+
"card_dir": str(self.card_dir),
|
|
201
|
+
"key": str(self.key),
|
|
202
|
+
"cert": str(self.cert),
|
|
166
203
|
"uri": self.uri,
|
|
167
|
-
"
|
|
204
|
+
"softhsm": str(self._softhsm2_conf),
|
|
205
|
+
"ca_name": self.ca_name,
|
|
206
|
+
"slot": self.slot
|
|
168
207
|
}
|
|
169
208
|
|
|
170
209
|
@property
|
|
@@ -173,18 +212,10 @@ class VirtualCard(Card):
|
|
|
173
212
|
|
|
174
213
|
@softhsm2_conf.setter
|
|
175
214
|
def softhsm2_conf(self, conf: Path):
|
|
176
|
-
|
|
215
|
+
if not conf.exists():
|
|
216
|
+
raise FileNotFoundError(f"File {conf} doesn't exist")
|
|
177
217
|
self._softhsm2_conf = conf
|
|
178
218
|
|
|
179
|
-
@property
|
|
180
|
-
def user(self):
|
|
181
|
-
return self._user
|
|
182
|
-
|
|
183
|
-
@user.setter
|
|
184
|
-
def user(self, system_user):
|
|
185
|
-
self._user = system_user
|
|
186
|
-
self._nssdb = self.user.card_dir.joinpath("db")
|
|
187
|
-
|
|
188
219
|
@property
|
|
189
220
|
def service_location(self):
|
|
190
221
|
return self._service_location
|
|
@@ -217,19 +248,19 @@ class VirtualCard(Card):
|
|
|
217
248
|
NSS database) with pkcs11-tool.
|
|
218
249
|
"""
|
|
219
250
|
cmd = ["pkcs11-tool", "--module", "libsofthsm2.so", "--slot-index",
|
|
220
|
-
'0', "-w", self.
|
|
221
|
-
|
|
251
|
+
'0', "-w", self.key, "-y", "privkey", "--label",
|
|
252
|
+
"test_key", "-p", self.pin, "--set-id", "0",
|
|
222
253
|
"-d", "0"]
|
|
223
254
|
run(cmd, env={"SOFTHSM2_CONF": self._softhsm2_conf})
|
|
224
255
|
logger.debug(
|
|
225
|
-
f"User key {self.
|
|
256
|
+
f"User key {self.key} is added to virtual smart card")
|
|
226
257
|
|
|
227
258
|
cmd = ['pkcs11-tool', '--module', 'libsofthsm2.so', '--slot-index', "0",
|
|
228
|
-
'-w', self.
|
|
229
|
-
'--label',
|
|
259
|
+
'-w', self.cert, '-y', 'cert', '-p', self.pin,
|
|
260
|
+
'--label', "test_cert", '--set-id', "0", '-d', "0"]
|
|
230
261
|
run(cmd, env={"SOFTHSM2_CONF": self._softhsm2_conf})
|
|
231
262
|
logger.debug(
|
|
232
|
-
f"User certificate {self.
|
|
263
|
+
f"User certificate {self.cert} is added to virtual smart card")
|
|
233
264
|
|
|
234
265
|
# To get URI of the card, the card has to be inserted
|
|
235
266
|
# Virtual smart card can't be started without a cert and a key uploaded
|
|
@@ -245,23 +276,23 @@ class VirtualCard(Card):
|
|
|
245
276
|
required for each virtual card.
|
|
246
277
|
"""
|
|
247
278
|
|
|
248
|
-
|
|
249
|
-
"Can't proceed, SoftHSM2 conf
|
|
279
|
+
if not self._softhsm2_conf.exists():
|
|
280
|
+
raise FileNotFoundError("Can't proceed, SoftHSM2 conf not found.")
|
|
250
281
|
|
|
251
|
-
self.
|
|
282
|
+
self.card_dir.joinpath("tokens").mkdir(exist_ok=True)
|
|
252
283
|
|
|
253
284
|
p11lib = "/usr/lib64/pkcs11/libsofthsm2.so"
|
|
254
285
|
# Initialize SoftHSM2 token. An error would be raised if token with same
|
|
255
286
|
# label would be created.
|
|
256
287
|
cmd = ["softhsm2-util", "--init-token", "--free", "--label",
|
|
257
|
-
self.
|
|
258
|
-
"--pin", self.
|
|
288
|
+
self.name, "--so-pin", "12345678",
|
|
289
|
+
"--pin", self.pin]
|
|
259
290
|
run(cmd, env={"SOFTHSM2_CONF": self._softhsm2_conf}, check=True)
|
|
260
291
|
logger.debug(
|
|
261
|
-
f"SoftHSM token is initialized with label '{self.
|
|
292
|
+
f"SoftHSM token is initialized with label '{self.cardholder}'")
|
|
262
293
|
|
|
263
294
|
# Initialize NSS db
|
|
264
|
-
self._nssdb = self.
|
|
295
|
+
self._nssdb = self.card_dir.joinpath("db")
|
|
265
296
|
self._nssdb.mkdir(exist_ok=True)
|
|
266
297
|
run(f"modutil -create -dbdir sql:{self._nssdb} -force", check=True)
|
|
267
298
|
logger.debug(f"NSS database is initialized in {self._nssdb}")
|
|
@@ -275,11 +306,140 @@ class VirtualCard(Card):
|
|
|
275
306
|
# Create systemd service
|
|
276
307
|
with self._template.open() as tmp:
|
|
277
308
|
with self._service_location.open("w") as f:
|
|
278
|
-
f.write(tmp.read().format(username=self.
|
|
309
|
+
f.write(tmp.read().format(username=self.cardholder,
|
|
279
310
|
softhsm2_conf=self._softhsm2_conf,
|
|
280
|
-
card_dir=self.
|
|
311
|
+
card_dir=self.card_dir))
|
|
281
312
|
|
|
282
313
|
logger.debug(f"Service is created in {self._service_location}")
|
|
283
314
|
run("systemctl daemon-reload")
|
|
284
315
|
|
|
285
316
|
return self
|
|
317
|
+
|
|
318
|
+
def gen_csr(self):
|
|
319
|
+
"""
|
|
320
|
+
Method for generating user specific CSR file that would be sent to the
|
|
321
|
+
CA for generating the certificate. CSR is generated using 'openssl`
|
|
322
|
+
command based on template CNF file.
|
|
323
|
+
"""
|
|
324
|
+
csr_path = self.card_dir.joinpath(f"csr-{self.cardholder}.csr")
|
|
325
|
+
if self.user.user_type == UserType.local:
|
|
326
|
+
cmd = ["openssl", "req", "-new", "-nodes", "-key", self.key,
|
|
327
|
+
"-reqexts", "req_exts", "-config", self.cnf,
|
|
328
|
+
"-out", csr_path]
|
|
329
|
+
else:
|
|
330
|
+
if not self.key:
|
|
331
|
+
raise SCAutolibException("Can't generate CSR because private "
|
|
332
|
+
"key is not set")
|
|
333
|
+
cmd = ["openssl", "req", "-new", "-days", "365",
|
|
334
|
+
"-nodes", "-key", self.key, "-out",
|
|
335
|
+
csr_path, "-subj", f"/CN={self.cardholder}"]
|
|
336
|
+
run(cmd)
|
|
337
|
+
return csr_path
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class PhysicalCard(Card):
|
|
341
|
+
"""
|
|
342
|
+
:TODO PhysicalCard is not yet tested, it's Work In Progress
|
|
343
|
+
This class provides methods allowing to manipulate physical cards
|
|
344
|
+
connected via removinator.
|
|
345
|
+
"""
|
|
346
|
+
_inserted: bool = False
|
|
347
|
+
|
|
348
|
+
name: str = None
|
|
349
|
+
pin: str = None
|
|
350
|
+
cardholder: str = None
|
|
351
|
+
CN: str = None
|
|
352
|
+
UID: str = None
|
|
353
|
+
expires: str = None
|
|
354
|
+
card_type: str = None
|
|
355
|
+
ca_name: str = None
|
|
356
|
+
slot: str = None
|
|
357
|
+
uri: str = None
|
|
358
|
+
card_dir: Path = None
|
|
359
|
+
|
|
360
|
+
def __init__(self, card_data: dict = None, card_dir: Path = None):
|
|
361
|
+
"""
|
|
362
|
+
TODO this is not yet tested, insert and remove methods need to be
|
|
363
|
+
implemented with removinator
|
|
364
|
+
Initialise object for physical smart card. Constructor of the base class
|
|
365
|
+
is also used.
|
|
366
|
+
"""
|
|
367
|
+
self.card_data = card_data
|
|
368
|
+
# Not sure we will need following, let's see later
|
|
369
|
+
self.name = card_data["name"]
|
|
370
|
+
self.pin = card_data["pin"]
|
|
371
|
+
self.cardholder = card_data["cardholder"]
|
|
372
|
+
self.CN = card_data["CN"]
|
|
373
|
+
self.UID = card_data["UID"]
|
|
374
|
+
# self.expires = card_data["expires"]
|
|
375
|
+
# self.card_type = card_data["card_type"]
|
|
376
|
+
# self.ca_name = card_data["ca_name"]
|
|
377
|
+
self.slot = card_data["slot"]
|
|
378
|
+
self.uri = card_data["uri"]
|
|
379
|
+
self.card_dir = card_dir
|
|
380
|
+
if not self.card_dir.exists():
|
|
381
|
+
raise FileNotFoundError("Card root directory doesn't exists")
|
|
382
|
+
|
|
383
|
+
self.dump_file = LIB_DUMP_CARDS.joinpath(f"{self.name}.json")
|
|
384
|
+
|
|
385
|
+
def __call__(self, insert: bool):
|
|
386
|
+
"""
|
|
387
|
+
Call method for physical smart card. It would be used in the context
|
|
388
|
+
manager.
|
|
389
|
+
|
|
390
|
+
:param insert: True if the card should be inserted, False otherwise
|
|
391
|
+
:type insert: bool
|
|
392
|
+
"""
|
|
393
|
+
if insert:
|
|
394
|
+
self.insert()
|
|
395
|
+
return self.__enter__()
|
|
396
|
+
|
|
397
|
+
def __enter__(self):
|
|
398
|
+
"""
|
|
399
|
+
Start of context manager for physical smart card. The card would be
|
|
400
|
+
inserted if ``insert`` parameter in constructor is specified.
|
|
401
|
+
|
|
402
|
+
:return: self
|
|
403
|
+
"""
|
|
404
|
+
return self
|
|
405
|
+
|
|
406
|
+
def __exit__(self, exp_type, exp_value, exp_traceback):
|
|
407
|
+
"""
|
|
408
|
+
End of context manager for physical smart card. If any exception was
|
|
409
|
+
raised in the current context, it would be logged as an error.
|
|
410
|
+
|
|
411
|
+
:param exp_type: Type of the exception
|
|
412
|
+
:param exp_value: Value for the exception
|
|
413
|
+
:param exp_traceback: Traceback of the exception
|
|
414
|
+
"""
|
|
415
|
+
if exp_type is not None:
|
|
416
|
+
logger.error("Exception in physical smart card context")
|
|
417
|
+
logger.error(format_exc())
|
|
418
|
+
if self._inserted:
|
|
419
|
+
self.remove()
|
|
420
|
+
|
|
421
|
+
def to_dict(self):
|
|
422
|
+
"""
|
|
423
|
+
Customising default property for better serialisation for storing to
|
|
424
|
+
JSON format.
|
|
425
|
+
|
|
426
|
+
:return: dictionary with all values. Path objects are typed to string.
|
|
427
|
+
:rtype: dict
|
|
428
|
+
"""
|
|
429
|
+
return self.card_data
|
|
430
|
+
|
|
431
|
+
@property
|
|
432
|
+
def user(self):
|
|
433
|
+
return self.cardholder
|
|
434
|
+
|
|
435
|
+
def insert(self):
|
|
436
|
+
"""
|
|
437
|
+
Insert physical card using removinator
|
|
438
|
+
"""
|
|
439
|
+
...
|
|
440
|
+
|
|
441
|
+
def remove(self):
|
|
442
|
+
"""
|
|
443
|
+
Remove physical card using removinator
|
|
444
|
+
"""
|
|
445
|
+
...
|