Mon, 26 Dec 2011 19:31:22 +0100
Updated copyright for 2012.
# -*- coding: utf-8 -*- # Copyright (c) 2010 - 2012 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing a dialog to show and edit all certificates. """ from PyQt4.QtCore import pyqtSlot, Qt, QByteArray, QFile, QFileInfo, QIODevice from PyQt4.QtGui import QDialog, QTreeWidgetItem try: from PyQt4.QtNetwork import QSslCertificate, QSslSocket, QSslConfiguration, QSsl except ImportError: pass from E5Gui import E5MessageBox, E5FileDialog from .Ui_SslCertificatesDialog import Ui_SslCertificatesDialog try: from .SslInfoDialog import SslInfoDialog except ImportError: pass import Preferences import Utilities class SslCertificatesDialog(QDialog, Ui_SslCertificatesDialog): """ Class implementing a dialog to show and edit all certificates. """ CertRole = Qt.UserRole + 1 def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget (QWidget) """ super().__init__(parent) self.setupUi(self) self.__populateServerCertificatesTree() self.__populateCaCertificatesTree() def __populateServerCertificatesTree(self): """ Private slot to populate the server certificates tree. """ certificateDict = Preferences.toDict( Preferences.Prefs.settings.value("Help/CaCertificatesDict")) for server in certificateDict: for cert in QSslCertificate.fromData(certificateDict[server]): self.__createServerCertificateEntry(server, cert) self.serversCertificatesTree.expandAll() for i in range(self.serversCertificatesTree.columnCount()): self.serversCertificatesTree.resizeColumnToContents(i) def __createServerCertificateEntry(self, server, cert): """ Private method to create a server certificate entry. @param server server name of the certificate (string) @param cert certificate to insert (QSslCertificate) """ # step 1: extract the info to be shown organisation = Qt.escape(Utilities.decodeString( cert.subjectInfo(QSslCertificate.Organization))) if organisation is None or organisation == "": organisation = self.trUtf8("(Unknown)") commonName = Qt.escape(Utilities.decodeString( cert.subjectInfo(QSslCertificate.CommonName))) if commonName is None or commonName == "": commonName = self.trUtf8("(Unknown common name)") expiryDate = Qt.escape(cert.expiryDate().toString("yyyy-MM-dd")) # step 2: create the entry items = self.serversCertificatesTree.findItems(organisation, Qt.MatchFixedString | Qt.MatchCaseSensitive) if len(items) == 0: parent = QTreeWidgetItem(self.serversCertificatesTree, [organisation]) else: parent = items[0] itm = QTreeWidgetItem(parent, [commonName, server, expiryDate]) itm.setData(0, self.CertRole, cert) @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) def on_serversCertificatesTree_currentItemChanged(self, current, previous): """ Private slot handling a change of the current item in the server certificates list. @param current new current item (QTreeWidgetItem) @param previous previous current item (QTreeWidgetItem) """ enable = current is not None and current.parent() is not None self.serversViewButton.setEnabled(enable) self.serversDeleteButton.setEnabled(enable) self.serversExportButton.setEnabled(enable) @pyqtSlot() def on_serversViewButton_clicked(self): """ Private slot to show data of the selected server certificate. """ cert = self.serversCertificatesTree.currentItem().data(0, self.CertRole) dlg = SslInfoDialog(cert, self) dlg.exec_() @pyqtSlot() def on_serversDeleteButton_clicked(self): """ Private slot to delete the selected server certificate. """ itm = self.serversCertificatesTree.currentItem() res = E5MessageBox.yesNo(self, self.trUtf8("Delete Server Certificate"), self.trUtf8("""<p>Shall the server certificate really be deleted?</p>""" """<p>{0}</p>""" """<p>If the server certificate is deleted, the normal security""" """ checks will be reinstantiated and the server has to""" """ present a valid certificate.</p>""")\ .format(itm.text(0))) if res: server = itm.text(1) cert = self.serversCertificatesTree.currentItem().data(0, self.CertRole) # delete the selected entry and it's parent entry, if it was the only one parent = itm.parent() parent.takeChild(parent.indexOfChild(itm)) if parent.childCount() == 0: self.serversCertificatesTree.takeTopLevelItem( self.serversCertificatesTree.indexOfTopLevelItem(parent)) # delete the certificate from the user certificate store certificateDict = Preferences.toDict( Preferences.Prefs.settings.value("Help/CaCertificatesDict")) if server in certificateDict: certs = QSslCertificate.fromData(certificateDict[server]) if cert in certs: certs.remove(cert) if certs: pems = QByteArray() for cert in certs: pems.append(cert.toPem() + '\n') certificateDict[server] = pems else: del certificateDict[server] Preferences.Prefs.settings.setValue("Help/CaCertificatesDict", certificateDict) # delete the certificate from the default certificates self.__updateDefaultConfiguration() @pyqtSlot() def on_serversImportButton_clicked(self): """ Private slot to import server certificates. """ certs = self.__importCertificate() if certs: server = "*" certificateDict = Preferences.toDict( Preferences.Prefs.settings.value("Help/CaCertificatesDict")) if server in certificateDict: sCerts = QSslCertificate.fromData(certificateDict[server]) else: sCerts = [] pems = QByteArray() for cert in certs: if cert in sCerts: E5MessageBox.warning(self, self.trUtf8("Import Certificate"), self.trUtf8("""<p>The certificate <b>{0}</b> already exists.""" """ Skipping.</p>""") .format(Utilities.decodeString( cert.subjectInfo(QSslCertificate.CommonName)))) else: pems.append(cert.toPem() + '\n') if server not in certificateDict: certificateDict[server] = QByteArray() certificateDict[server].append(pems) Preferences.Prefs.settings.setValue("Help/CaCertificatesDict", certificateDict) self.serversCertificatesTree.clear() self.__populateServerCertificatesTree() self.__updateDefaultConfiguration() @pyqtSlot() def on_serversExportButton_clicked(self): """ Private slot to export the selected server certificate. """ cert = self.serversCertificatesTree.currentItem().data(0, self.CertRole) fname = self.serversCertificatesTree.currentItem().text(0)\ .replace(" ", "").replace("\t", "") self.__exportCertificate(fname, cert) def __updateDefaultConfiguration(self): """ Private method to update the default SSL configuration. """ caList = self.__getSystemCaCertificates() certificateDict = Preferences.toDict( Preferences.Prefs.settings.value("Help/CaCertificatesDict")) for server in certificateDict: for cert in QSslCertificate.fromData(certificateDict[server]): if cert not in caList: caList.append(cert) sslCfg = QSslConfiguration.defaultConfiguration() sslCfg.setCaCertificates(caList) QSslConfiguration.setDefaultConfiguration(sslCfg) def __getSystemCaCertificates(self): """ Private method to get the list of system certificates. @return list of system certificates (list of QSslCertificate) """ caList = QSslCertificate.fromData(Preferences.toByteArray( Preferences.Prefs.settings.value("Help/SystemCertificates"))) if not caList: caList = QSslSocket.systemCaCertificates() return caList def __populateCaCertificatesTree(self): """ Private slot to populate the CA certificates tree. """ for cert in self.__getSystemCaCertificates(): self.__createCaCertificateEntry(cert) self.caCertificatesTree.expandAll() for i in range(self.caCertificatesTree.columnCount()): self.caCertificatesTree.resizeColumnToContents(i) self.caCertificatesTree.sortItems(0, Qt.AscendingOrder) def __createCaCertificateEntry(self, cert): """ Private method to create a CA certificate entry. @param cert certificate to insert (QSslCertificate) """ # step 1: extract the info to be shown organisation = Qt.escape(Utilities.decodeString( cert.subjectInfo(QSslCertificate.Organization))) if organisation is None or organisation == "": organisation = self.trUtf8("(Unknown)") commonName = Qt.escape(Utilities.decodeString( cert.subjectInfo(QSslCertificate.CommonName))) if commonName is None or commonName == "": commonName = self.trUtf8("(Unknown common name)") expiryDate = Qt.escape(cert.expiryDate().toString("yyyy-MM-dd")) # step 2: create the entry items = self.caCertificatesTree.findItems(organisation, Qt.MatchFixedString | Qt.MatchCaseSensitive) if len(items) == 0: parent = QTreeWidgetItem(self.caCertificatesTree, [organisation]) else: parent = items[0] itm = QTreeWidgetItem(parent, [commonName, expiryDate]) itm.setData(0, self.CertRole, cert) @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) def on_caCertificatesTree_currentItemChanged(self, current, previous): """ Private slot handling a change of the current item in the CA certificates list. @param current new current item (QTreeWidgetItem) @param previous previous current item (QTreeWidgetItem) """ enable = current is not None and current.parent() is not None self.caViewButton.setEnabled(enable) self.caDeleteButton.setEnabled(enable) self.caExportButton.setEnabled(enable) @pyqtSlot() def on_caViewButton_clicked(self): """ Private slot to show data of the selected CA certificate. """ cert = self.caCertificatesTree.currentItem().data(0, self.CertRole) dlg = SslInfoDialog(cert, self) dlg.exec_() @pyqtSlot() def on_caDeleteButton_clicked(self): """ Private slot to delete the selected CA certificate. """ itm = self.caCertificatesTree.currentItem() res = E5MessageBox.yesNo(self, self.trUtf8("Delete CA Certificate"), self.trUtf8("""<p>Shall the CA certificate really be deleted?</p>""" """<p>{0}</p>""" """<p>If the CA certificate is deleted, the browser""" """ will not trust any certificate issued by this CA.</p>""")\ .format(itm.text(0))) if res: cert = self.caCertificatesTree.currentItem().data(0, self.CertRole) # delete the selected entry and it's parent entry, if it was the only one parent = itm.parent() parent.takeChild(parent.indexOfChild(itm)) if parent.childCount() == 0: self.caCertificatesTree.takeTopLevelItem( self.caCertificatesTree.indexOfTopLevelItem(parent)) # delete the certificate from the CA certificate store caCerts = self.__getSystemCaCertificates() if cert in caCerts: caCerts.remove(cert) pems = QByteArray() for cert in caCerts: pems.append(cert.toPem() + '\n') Preferences.Prefs.settings.setValue("Help/SystemCertificates", pems) # delete the certificate from the default certificates self.__updateDefaultConfiguration() @pyqtSlot() def on_caImportButton_clicked(self): """ Private slot to import server certificates. """ certs = self.__importCertificate() if certs: caCerts = self.__getSystemCaCertificates() for cert in certs: if cert in caCerts: E5MessageBox.warning(self, self.trUtf8("Import Certificate"), self.trUtf8("""<p>The certificate <b>{0}</b> already exists.""" """ Skipping.</p>""") .format(Utilities.decodeString( cert.subjectInfo(QSslCertificate.CommonName)))) else: caCerts.append(cert) pems = QByteArray() for cert in caCerts: pems.append(cert.toPem() + '\n') Preferences.Prefs.settings.setValue("Help/SystemCertificates", pems) self.caCertificatesTree.clear() self.__populateCaCertificatesTree() self.__updateDefaultConfiguration() @pyqtSlot() def on_caExportButton_clicked(self): """ Private slot to export the selected CA certificate. """ cert = self.caCertificatesTree.currentItem().data(0, self.CertRole) fname = self.caCertificatesTree.currentItem().text(0)\ .replace(" ", "").replace("\t", "") self.__exportCertificate(fname, cert) def __exportCertificate(self, name, cert): """ Private slot to export a certificate. @param name default file name without extension (string) @param cert certificate to be exported (QSslCertificate) """ if cert is not None: fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( self, self.trUtf8("Export Certificate"), name, self.trUtf8("Certificate File (PEM) (*.pem);;" "Certificate File (DER) (*.der)"), None, E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) if fname: ext = QFileInfo(fname).suffix() if not ext or ext not in ["pem", "der"]: ex = selectedFilter.split("(*")[1].split(")")[0] if ex: fname += ex if QFileInfo(fname).exists(): res = E5MessageBox.yesNo(self, self.trUtf8("Export Certificate"), self.trUtf8("<p>The file <b>{0}</b> already exists." " Overwrite it?</p>").format(fname), icon=E5MessageBox.Warning) if not res: return f = QFile(fname) if not f.open(QIODevice.WriteOnly): E5MessageBox.critical(self, self.trUtf8("Export Certificate"), self.trUtf8("""<p>The certificate could not be written to file""" """ <b>{0}</b></p><p>Error: {1}</p>""") .format(fname, f.errorString())) return if fname.endswith(".pem"): crt = cert.toPem() else: crt = cert.toDer() f.write(crt) f.close() def __importCertificate(self): """ Private method to read a certificate. @return certificates read (list of QSslCertificate) """ fname = E5FileDialog.getOpenFileName( self, self.trUtf8("Import Certificate"), "", self.trUtf8("Certificate Files (*.pem *.crt *.der *.cer *.ca);;" "All Files (*)")) if fname: f = QFile(fname) if not f.open(QIODevice.ReadOnly): E5MessageBox.critical(self, self.trUtf8("Export Certificate"), self.trUtf8("""<p>The certificate could not be read from file""" """ <b>{0}</b></p><p>Error: {1}</p>""") .format(fname, f.errorString())) return [] crt = f.readAll() f.close() cert = QSslCertificate.fromData(crt, QSsl.Pem) if not cert: cert = QSslCertificate.fromData(crt, QSsl.Der) return cert return []