SCAutolib 2.0.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/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 "_ipa_server_ip" in cnt.keys():
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
- else:
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
- logger.debug(f"CA {type(ca)} is restored from file {json_file}")
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
- dump_file = LIB_DUMP_CAS.joinpath("local-ca.json")
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
- assert self.root_dir is not None
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: Path = Path("/etc/sssd/pki/sssd_auth_ca_db.pem")
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. If directory
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
- with self._ca_cert.open("r") as f_cert:
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 store
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("Maximum kerberos password lifetime is set to 365 days")
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, watchers=[kinitpass], in_stream=False)
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, assertion exception
31
- would be raised
30
+ with regular expression. If URI is not matched, exception is raised.
32
31
 
33
- :raise: AssertionError
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
- assert len(urls) == 1, f"Card URI is not matched. Matched URIs: {urls}"
39
- self.uri = urls[0]
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, **kwars):
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["type"] == "virtual":
66
- assert "user" in kwars.keys(), \
67
- "No user is provided to load the card."
68
- card = VirtualCard(user=kwars["user"],
69
- softhsm2_conf=Path(cnt["softhsm"]))
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['type']}")
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 method for manipulating with virtual smart card. Virtual
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
- def __init__(self, user, softhsm2_conf: Path = None):
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 user: User of this card
103
- :type user: SCAutolib.models.user.User
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.user = user
109
- assert self.user.card_dir.exists(), "Card root directory doesn't exists"
110
-
111
- self.dump_file = LIB_DUMP_CARDS.joinpath(f"card-{user.username}.json")
112
-
113
- self._private_key = self.user.key
114
- self._cert = self.user.cert
115
-
116
- self._service_name = f"virt-sc-{self.user.username}"
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.dump_file = LIB_DUMP_CARDS.joinpath(
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("/home", self.user.username, "softhsm2.conf")
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
- assert self._service_location.exists(), \
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
- "softhsm": str(self._softhsm2_conf),
165
- "type": "virtual",
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
- "username": self.user.username
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
- assert conf.exists(), "File doesn't exist"
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._user.key, "-y", "privkey", "--label",
221
- f"'{self._user.username}'", "-p", self._user.pin, "--set-id", "0",
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._user.key} is added to virtual smart card")
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._user.cert, '-y', 'cert', '-p', self._user.pin,
229
- '--label', f"'{self._user.username}'", '--set-id', "0", '-d', "0"]
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._user.cert} is added to virtual smart card")
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
- assert self._softhsm2_conf.exists(), \
249
- "Can't proceed, SoftHSM2 conf doesn't exist"
279
+ if not self._softhsm2_conf.exists():
280
+ raise FileNotFoundError("Can't proceed, SoftHSM2 conf not found.")
250
281
 
251
- self.user.card_dir.joinpath("tokens").mkdir(exist_ok=True)
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.user.username, "--so-pin", "12345678",
258
- "--pin", self.user.pin]
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.user.username}'")
292
+ f"SoftHSM token is initialized with label '{self.cardholder}'")
262
293
 
263
294
  # Initialize NSS db
264
- self._nssdb = self.user.card_dir.joinpath("db")
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.user.username,
309
+ f.write(tmp.read().format(username=self.cardholder,
279
310
  softhsm2_conf=self._softhsm2_conf,
280
- card_dir=self.user.card_dir))
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
+ ...