E5Network/E5SslCertificatesDialog.py

changeset 2359
ef81d2d0a031
child 2361
fe8bccb78a8d
equal deleted inserted replaced
2357:f6a2cbf3f514 2359:ef81d2d0a031
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2010 - 2013 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to show and edit all certificates.
8 """
9
10 from PyQt4.QtCore import pyqtSlot, Qt, QByteArray, QFile, QFileInfo, QIODevice, \
11 qVersion
12 from PyQt4.QtGui import QDialog, QTreeWidgetItem
13 try:
14 from PyQt4.QtNetwork import QSslCertificate, QSslSocket, QSslConfiguration, QSsl
15 except ImportError:
16 pass
17
18 from E5Gui import E5MessageBox, E5FileDialog
19
20 from .Ui_E5SslCertificatesDialog import Ui_E5SslCertificatesDialog
21
22 try:
23 from E5Network.E5SslInfoDialog import E5SslInfoDialog
24 except ImportError:
25 pass
26
27 import Preferences
28 import Utilities
29
30
31 class E5SslCertificatesDialog(QDialog, Ui_E5SslCertificatesDialog):
32 """
33 Class implementing a dialog to show and edit all certificates.
34 """
35 CertRole = Qt.UserRole + 1
36
37 def __init__(self, parent=None):
38 """
39 Constructor
40
41 @param parent reference to the parent widget (QWidget)
42 """
43 super().__init__(parent)
44 self.setupUi(self)
45
46 self.__populateServerCertificatesTree()
47 self.__populateCaCertificatesTree()
48
49 def __populateServerCertificatesTree(self):
50 """
51 Private slot to populate the server certificates tree.
52 """
53 certificateDict = Preferences.toDict(
54 Preferences.Prefs.settings.value("Ssl/CaCertificatesDict"))
55 for server in certificateDict:
56 for cert in QSslCertificate.fromData(certificateDict[server]):
57 self.__createServerCertificateEntry(server, cert)
58
59 self.serversCertificatesTree.expandAll()
60 for i in range(self.serversCertificatesTree.columnCount()):
61 self.serversCertificatesTree.resizeColumnToContents(i)
62
63 def __createServerCertificateEntry(self, server, cert):
64 """
65 Private method to create a server certificate entry.
66
67 @param server server name of the certificate (string)
68 @param cert certificate to insert (QSslCertificate)
69 """
70 # step 1: extract the info to be shown
71 if qVersion() >= "5.0.0":
72 organisation = Utilities.decodeString(
73 ", ".join(cert.subjectInfo(QSslCertificate.Organization)))
74 commonName = Utilities.decodeString(
75 ", ".join(cert.subjectInfo(QSslCertificate.CommonName)))
76 else:
77 organisation = Utilities.decodeString(
78 cert.subjectInfo(QSslCertificate.Organization))
79 commonName = Utilities.decodeString(
80 cert.subjectInfo(QSslCertificate.CommonName))
81 if organisation is None or organisation == "":
82 organisation = self.trUtf8("(Unknown)")
83 if commonName is None or commonName == "":
84 commonName = self.trUtf8("(Unknown common name)")
85 expiryDate = cert.expiryDate().toString("yyyy-MM-dd")
86
87 # step 2: create the entry
88 items = self.serversCertificatesTree.findItems(organisation,
89 Qt.MatchFixedString | Qt.MatchCaseSensitive)
90 if len(items) == 0:
91 parent = QTreeWidgetItem(self.serversCertificatesTree, [organisation])
92 else:
93 parent = items[0]
94
95 itm = QTreeWidgetItem(parent, [commonName, server, expiryDate])
96 itm.setData(0, self.CertRole, cert)
97
98 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
99 def on_serversCertificatesTree_currentItemChanged(self, current, previous):
100 """
101 Private slot handling a change of the current item in the
102 server certificates list.
103
104 @param current new current item (QTreeWidgetItem)
105 @param previous previous current item (QTreeWidgetItem)
106 """
107 enable = current is not None and current.parent() is not None
108 self.serversViewButton.setEnabled(enable)
109 self.serversDeleteButton.setEnabled(enable)
110 self.serversExportButton.setEnabled(enable)
111
112 @pyqtSlot()
113 def on_serversViewButton_clicked(self):
114 """
115 Private slot to show data of the selected server certificate.
116 """
117 cert = self.serversCertificatesTree.currentItem().data(0, self.CertRole)
118 dlg = E5SslInfoDialog(cert, self)
119 dlg.exec_()
120
121 @pyqtSlot()
122 def on_serversDeleteButton_clicked(self):
123 """
124 Private slot to delete the selected server certificate.
125 """
126 itm = self.serversCertificatesTree.currentItem()
127 res = E5MessageBox.yesNo(self,
128 self.trUtf8("Delete Server Certificate"),
129 self.trUtf8("""<p>Shall the server certificate really be deleted?</p>"""
130 """<p>{0}</p>"""
131 """<p>If the server certificate is deleted, the normal security"""
132 """ checks will be reinstantiated and the server has to"""
133 """ present a valid certificate.</p>""")\
134 .format(itm.text(0)))
135 if res:
136 server = itm.text(1)
137 cert = self.serversCertificatesTree.currentItem().data(0, self.CertRole)
138
139 # delete the selected entry and it's parent entry, if it was the only one
140 parent = itm.parent()
141 parent.takeChild(parent.indexOfChild(itm))
142 if parent.childCount() == 0:
143 self.serversCertificatesTree.takeTopLevelItem(
144 self.serversCertificatesTree.indexOfTopLevelItem(parent))
145
146 # delete the certificate from the user certificate store
147 certificateDict = Preferences.toDict(
148 Preferences.Prefs.settings.value("Ssl/CaCertificatesDict"))
149 if server in certificateDict:
150 certs = QSslCertificate.fromData(certificateDict[server])
151 if cert in certs:
152 certs.remove(cert)
153 if certs:
154 pems = QByteArray()
155 for cert in certs:
156 pems.append(cert.toPem() + '\n')
157 certificateDict[server] = pems
158 else:
159 del certificateDict[server]
160 Preferences.Prefs.settings.setValue("Ssl/CaCertificatesDict",
161 certificateDict)
162
163 # delete the certificate from the default certificates
164 self.__updateDefaultConfiguration()
165
166 @pyqtSlot()
167 def on_serversImportButton_clicked(self):
168 """
169 Private slot to import server certificates.
170 """
171 certs = self.__importCertificate()
172 if certs:
173 server = "*"
174 certificateDict = Preferences.toDict(
175 Preferences.Prefs.settings.value("Ssl/CaCertificatesDict"))
176 if server in certificateDict:
177 sCerts = QSslCertificate.fromData(certificateDict[server])
178 else:
179 sCerts = []
180
181 pems = QByteArray()
182 for cert in certs:
183 if cert in sCerts:
184 if qVersion() >= "5.0.0":
185 commonStr = ", ".join(
186 cert.subjectInfo(QSslCertificate.CommonName))
187 else:
188 commonStr = cert.subjectInfo(QSslCertificate.CommonName)
189 E5MessageBox.warning(self,
190 self.trUtf8("Import Certificate"),
191 self.trUtf8("""<p>The certificate <b>{0}</b> already exists."""
192 """ Skipping.</p>""")
193 .format(Utilities.decodeString(commonStr)))
194 else:
195 pems.append(cert.toPem() + '\n')
196 if server not in certificateDict:
197 certificateDict[server] = QByteArray()
198 certificateDict[server].append(pems)
199 Preferences.Prefs.settings.setValue("Ssl/CaCertificatesDict",
200 certificateDict)
201
202 self.serversCertificatesTree.clear()
203 self.__populateServerCertificatesTree()
204
205 self.__updateDefaultConfiguration()
206
207 @pyqtSlot()
208 def on_serversExportButton_clicked(self):
209 """
210 Private slot to export the selected server certificate.
211 """
212 cert = self.serversCertificatesTree.currentItem().data(0, self.CertRole)
213 fname = self.serversCertificatesTree.currentItem().text(0)\
214 .replace(" ", "").replace("\t", "")
215 self.__exportCertificate(fname, cert)
216
217 def __updateDefaultConfiguration(self):
218 """
219 Private method to update the default SSL configuration.
220 """
221 caList = self.__getSystemCaCertificates()
222 certificateDict = Preferences.toDict(
223 Preferences.Prefs.settings.value("Ssl/CaCertificatesDict"))
224 for server in certificateDict:
225 for cert in QSslCertificate.fromData(certificateDict[server]):
226 if cert not in caList:
227 caList.append(cert)
228 sslCfg = QSslConfiguration.defaultConfiguration()
229 sslCfg.setCaCertificates(caList)
230 QSslConfiguration.setDefaultConfiguration(sslCfg)
231
232 def __getSystemCaCertificates(self):
233 """
234 Private method to get the list of system certificates.
235
236 @return list of system certificates (list of QSslCertificate)
237 """
238 caList = QSslCertificate.fromData(Preferences.toByteArray(
239 Preferences.Prefs.settings.value("Help/SystemCertificates")))
240 if not caList:
241 caList = QSslSocket.systemCaCertificates()
242 return caList
243
244 def __populateCaCertificatesTree(self):
245 """
246 Private slot to populate the CA certificates tree.
247 """
248 for cert in self.__getSystemCaCertificates():
249 self.__createCaCertificateEntry(cert)
250
251 self.caCertificatesTree.expandAll()
252 for i in range(self.caCertificatesTree.columnCount()):
253 self.caCertificatesTree.resizeColumnToContents(i)
254 self.caCertificatesTree.sortItems(0, Qt.AscendingOrder)
255
256 def __createCaCertificateEntry(self, cert):
257 """
258 Private method to create a CA certificate entry.
259
260 @param cert certificate to insert (QSslCertificate)
261 """
262 # step 1: extract the info to be shown
263 if qVersion() >= "5.0.0":
264 organisation = Utilities.decodeString(
265 ", ".join(cert.subjectInfo(QSslCertificate.Organization)))
266 commonName = Utilities.decodeString(
267 ", ".join(cert.subjectInfo(QSslCertificate.CommonName)))
268 else:
269 organisation = Utilities.decodeString(
270 cert.subjectInfo(QSslCertificate.Organization))
271 commonName = Utilities.decodeString(
272 cert.subjectInfo(QSslCertificate.CommonName))
273 if organisation is None or organisation == "":
274 organisation = self.trUtf8("(Unknown)")
275 if commonName is None or commonName == "":
276 commonName = self.trUtf8("(Unknown common name)")
277 expiryDate = cert.expiryDate().toString("yyyy-MM-dd")
278
279 # step 2: create the entry
280 items = self.caCertificatesTree.findItems(organisation,
281 Qt.MatchFixedString | Qt.MatchCaseSensitive)
282 if len(items) == 0:
283 parent = QTreeWidgetItem(self.caCertificatesTree, [organisation])
284 else:
285 parent = items[0]
286
287 itm = QTreeWidgetItem(parent, [commonName, expiryDate])
288 itm.setData(0, self.CertRole, cert)
289
290 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
291 def on_caCertificatesTree_currentItemChanged(self, current, previous):
292 """
293 Private slot handling a change of the current item
294 in the CA certificates list.
295
296 @param current new current item (QTreeWidgetItem)
297 @param previous previous current item (QTreeWidgetItem)
298 """
299 enable = current is not None and current.parent() is not None
300 self.caViewButton.setEnabled(enable)
301 self.caDeleteButton.setEnabled(enable)
302 self.caExportButton.setEnabled(enable)
303
304 @pyqtSlot()
305 def on_caViewButton_clicked(self):
306 """
307 Private slot to show data of the selected CA certificate.
308 """
309 cert = self.caCertificatesTree.currentItem().data(0, self.CertRole)
310 dlg = E5SslInfoDialog(cert, self)
311 dlg.exec_()
312
313 @pyqtSlot()
314 def on_caDeleteButton_clicked(self):
315 """
316 Private slot to delete the selected CA certificate.
317 """
318 itm = self.caCertificatesTree.currentItem()
319 res = E5MessageBox.yesNo(self,
320 self.trUtf8("Delete CA Certificate"),
321 self.trUtf8("""<p>Shall the CA certificate really be deleted?</p>"""
322 """<p>{0}</p>"""
323 """<p>If the CA certificate is deleted, the browser"""
324 """ will not trust any certificate issued by this CA.</p>""")\
325 .format(itm.text(0)))
326 if res:
327 cert = self.caCertificatesTree.currentItem().data(0, self.CertRole)
328
329 # delete the selected entry and it's parent entry, if it was the only one
330 parent = itm.parent()
331 parent.takeChild(parent.indexOfChild(itm))
332 if parent.childCount() == 0:
333 self.caCertificatesTree.takeTopLevelItem(
334 self.caCertificatesTree.indexOfTopLevelItem(parent))
335
336 # delete the certificate from the CA certificate store
337 caCerts = self.__getSystemCaCertificates()
338 if cert in caCerts:
339 caCerts.remove(cert)
340 pems = QByteArray()
341 for cert in caCerts:
342 pems.append(cert.toPem() + '\n')
343 Preferences.Prefs.settings.setValue("Help/SystemCertificates", pems)
344
345 # delete the certificate from the default certificates
346 self.__updateDefaultConfiguration()
347
348 @pyqtSlot()
349 def on_caImportButton_clicked(self):
350 """
351 Private slot to import server certificates.
352 """
353 certs = self.__importCertificate()
354 if certs:
355 caCerts = self.__getSystemCaCertificates()
356 for cert in certs:
357 if cert in caCerts:
358 if qVersion() >= "5.0.0":
359 commonStr = ", ".join(
360 cert.subjectInfo(QSslCertificate.CommonName))
361 else:
362 commonStr = cert.subjectInfo(QSslCertificate.CommonName)
363 E5MessageBox.warning(self,
364 self.trUtf8("Import Certificate"),
365 self.trUtf8("""<p>The certificate <b>{0}</b> already exists."""
366 """ Skipping.</p>""")
367 .format(Utilities.decodeString(commonStr)))
368 else:
369 caCerts.append(cert)
370
371 pems = QByteArray()
372 for cert in caCerts:
373 pems.append(cert.toPem() + '\n')
374 Preferences.Prefs.settings.setValue("Help/SystemCertificates", pems)
375
376 self.caCertificatesTree.clear()
377 self.__populateCaCertificatesTree()
378
379 self.__updateDefaultConfiguration()
380
381 @pyqtSlot()
382 def on_caExportButton_clicked(self):
383 """
384 Private slot to export the selected CA certificate.
385 """
386 cert = self.caCertificatesTree.currentItem().data(0, self.CertRole)
387 fname = self.caCertificatesTree.currentItem().text(0)\
388 .replace(" ", "").replace("\t", "")
389 self.__exportCertificate(fname, cert)
390
391 def __exportCertificate(self, name, cert):
392 """
393 Private slot to export a certificate.
394
395 @param name default file name without extension (string)
396 @param cert certificate to be exported (QSslCertificate)
397 """
398 if cert is not None:
399 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
400 self,
401 self.trUtf8("Export Certificate"),
402 name,
403 self.trUtf8("Certificate File (PEM) (*.pem);;"
404 "Certificate File (DER) (*.der)"),
405 None,
406 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
407
408 if fname:
409 ext = QFileInfo(fname).suffix()
410 if not ext or ext not in ["pem", "der"]:
411 ex = selectedFilter.split("(*")[1].split(")")[0]
412 if ex:
413 fname += ex
414 if QFileInfo(fname).exists():
415 res = E5MessageBox.yesNo(self,
416 self.trUtf8("Export Certificate"),
417 self.trUtf8("<p>The file <b>{0}</b> already exists."
418 " Overwrite it?</p>").format(fname),
419 icon=E5MessageBox.Warning)
420 if not res:
421 return
422
423 f = QFile(fname)
424 if not f.open(QIODevice.WriteOnly):
425 E5MessageBox.critical(self,
426 self.trUtf8("Export Certificate"),
427 self.trUtf8("""<p>The certificate could not be written to file"""
428 """ <b>{0}</b></p><p>Error: {1}</p>""")
429 .format(fname, f.errorString()))
430 return
431
432 if fname.endswith(".pem"):
433 crt = cert.toPem()
434 else:
435 crt = cert.toDer()
436 f.write(crt)
437 f.close()
438
439 def __importCertificate(self):
440 """
441 Private method to read a certificate.
442
443 @return certificates read (list of QSslCertificate)
444 """
445 fname = E5FileDialog.getOpenFileName(
446 self,
447 self.trUtf8("Import Certificate"),
448 "",
449 self.trUtf8("Certificate Files (*.pem *.crt *.der *.cer *.ca);;"
450 "All Files (*)"))
451
452 if fname:
453 f = QFile(fname)
454 if not f.open(QIODevice.ReadOnly):
455 E5MessageBox.critical(self,
456 self.trUtf8("Export Certificate"),
457 self.trUtf8("""<p>The certificate could not be read from file"""
458 """ <b>{0}</b></p><p>Error: {1}</p>""")
459 .format(fname, f.errorString()))
460 return []
461
462 crt = f.readAll()
463 f.close()
464 cert = QSslCertificate.fromData(crt, QSsl.Pem)
465 if not cert:
466 cert = QSslCertificate.fromData(crt, QSsl.Der)
467
468 return cert
469
470 return []

eric ide

mercurial