--- a/src/eric7/EricNetwork/EricSslCertificatesDialog.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/EricNetwork/EricSslCertificatesDialog.py Wed Jul 13 14:55:47 2022 +0200 @@ -12,6 +12,7 @@ from PyQt6.QtCore import pyqtSlot, Qt, QByteArray from PyQt6.QtWidgets import QDialog, QTreeWidgetItem + with contextlib.suppress(ImportError): from PyQt6.QtNetwork import QSslCertificate, QSslConfiguration, QSsl @@ -29,92 +30,86 @@ """ Class implementing a dialog to show and edit all certificates. """ + CertRole = Qt.ItemDataRole.UserRole + 1 - + def __init__(self, parent=None): """ Constructor - + @param parent reference to the parent widget (QWidget) """ super().__init__(parent) self.setupUi(self) - - self.serversViewButton.setIcon( - UI.PixmapCache.getIcon("certificates")) - self.serversDeleteButton.setIcon( - UI.PixmapCache.getIcon("certificateDelete")) - self.serversExportButton.setIcon( - UI.PixmapCache.getIcon("certificateExport")) - self.serversImportButton.setIcon( - UI.PixmapCache.getIcon("certificateImport")) - - self.caViewButton.setIcon( - UI.PixmapCache.getIcon("certificates")) - self.caDeleteButton.setIcon( - UI.PixmapCache.getIcon("certificateDelete")) - self.caExportButton.setIcon( - UI.PixmapCache.getIcon("certificateExport")) - self.caImportButton.setIcon( - UI.PixmapCache.getIcon("certificateImport")) - + + self.serversViewButton.setIcon(UI.PixmapCache.getIcon("certificates")) + self.serversDeleteButton.setIcon(UI.PixmapCache.getIcon("certificateDelete")) + self.serversExportButton.setIcon(UI.PixmapCache.getIcon("certificateExport")) + self.serversImportButton.setIcon(UI.PixmapCache.getIcon("certificateImport")) + + self.caViewButton.setIcon(UI.PixmapCache.getIcon("certificates")) + self.caDeleteButton.setIcon(UI.PixmapCache.getIcon("certificateDelete")) + self.caExportButton.setIcon(UI.PixmapCache.getIcon("certificateExport")) + self.caImportButton.setIcon(UI.PixmapCache.getIcon("certificateImport")) + self.__populateServerCertificatesTree() self.__populateCaCertificatesTree() - + def __populateServerCertificatesTree(self): """ Private slot to populate the server certificates tree. """ certificateDict = Globals.toDict( - Preferences.getSettings().value("Ssl/CaCertificatesDict")) + Preferences.getSettings().value("Ssl/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 = Utilities.decodeString( - ", ".join(cert.subjectInfo( - QSslCertificate.SubjectInfo.Organization))) + ", ".join(cert.subjectInfo(QSslCertificate.SubjectInfo.Organization)) + ) commonName = Utilities.decodeString( - ", ".join(cert.subjectInfo( - QSslCertificate.SubjectInfo.CommonName))) + ", ".join(cert.subjectInfo(QSslCertificate.SubjectInfo.CommonName)) + ) if organisation is None or organisation == "": organisation = self.tr("(Unknown)") if commonName is None or commonName == "": commonName = self.tr("(Unknown common name)") expiryDate = cert.expiryDate().toString("yyyy-MM-dd") - + # step 2: create the entry items = self.serversCertificatesTree.findItems( organisation, - Qt.MatchFlag.MatchFixedString | Qt.MatchFlag.MatchCaseSensitive) + Qt.MatchFlag.MatchFixedString | Qt.MatchFlag.MatchCaseSensitive, + ) if len(items) == 0: - parent = QTreeWidgetItem( - self.serversCertificatesTree, [organisation]) + parent = QTreeWidgetItem(self.serversCertificatesTree, [organisation]) parent.setFirstColumnSpanned(True) else: parent = items[0] - + itm = QTreeWidgetItem(parent, [commonName, server, expiryDate]) itm.setData(0, self.CertRole, cert.toPem()) - + @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) """ @@ -122,7 +117,7 @@ self.serversViewButton.setEnabled(enable) self.serversDeleteButton.setEnabled(enable) self.serversExportButton.setEnabled(enable) - + @pyqtSlot() def on_serversViewButton_clicked(self): """ @@ -130,14 +125,15 @@ """ with contextlib.suppress(ImportError): from EricNetwork.EricSslCertificatesInfoDialog import ( - EricSslCertificatesInfoDialog + EricSslCertificatesInfoDialog, ) + cert = QSslCertificate.fromData( - self.serversCertificatesTree.currentItem().data( - 0, self.CertRole)) + self.serversCertificatesTree.currentItem().data(0, self.CertRole) + ) dlg = EricSslCertificatesInfoDialog(cert, self) dlg.exec() - + @pyqtSlot() def on_serversDeleteButton_clicked(self): """ @@ -147,48 +143,52 @@ res = EricMessageBox.yesNo( self, self.tr("Delete Server Certificate"), - self.tr("""<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))) + self.tr( + """<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) - + cert = self.serversCertificatesTree.currentItem().data(0, self.CertRole) + # delete the selected entry and its 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)) - + self.serversCertificatesTree.indexOfTopLevelItem(parent) + ) + # delete the certificate from the user certificate store certificateDict = Globals.toDict( - Preferences.getSettings().value("Ssl/CaCertificatesDict")) + Preferences.getSettings().value("Ssl/CaCertificatesDict") + ) if server in certificateDict: - certs = [c.toPem() for c in - QSslCertificate.fromData(certificateDict[server])] + certs = [ + c.toPem() for c in QSslCertificate.fromData(certificateDict[server]) + ] if cert in certs: certs.remove(cert) if certs: pems = QByteArray() for cert in certs: - pems.append(cert + b'\n') + pems.append(cert + b"\n") certificateDict[server] = pems else: del certificateDict[server] Preferences.getSettings().setValue( - "Ssl/CaCertificatesDict", - certificateDict) - + "Ssl/CaCertificatesDict", certificateDict + ) + # delete the certificate from the default certificates self.__updateDefaultConfiguration() - + @pyqtSlot() def on_serversImportButton_clicked(self): """ @@ -198,59 +198,63 @@ if certs: server = "*" certificateDict = Globals.toDict( - Preferences.getSettings().value("Ssl/CaCertificatesDict")) + Preferences.getSettings().value("Ssl/CaCertificatesDict") + ) if server in certificateDict: sCerts = QSslCertificate.fromData(certificateDict[server]) else: sCerts = [] - + pems = QByteArray() for cert in certs: if cert in sCerts: commonStr = ", ".join( - cert.subjectInfo( - QSslCertificate.SubjectInfo.CommonName)) + cert.subjectInfo(QSslCertificate.SubjectInfo.CommonName) + ) EricMessageBox.warning( self, self.tr("Import Certificate"), self.tr( """<p>The certificate <b>{0}</b> already exists.""" - """ Skipping.</p>""") - .format(Utilities.decodeString(commonStr))) + """ Skipping.</p>""" + ).format(Utilities.decodeString(commonStr)), + ) else: - pems.append(cert.toPem() + b'\n') + pems.append(cert.toPem() + b"\n") if server not in certificateDict: certificateDict[server] = QByteArray() certificateDict[server].append(pems) Preferences.getSettings().setValue( - "Ssl/CaCertificatesDict", - certificateDict) - + "Ssl/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) + cert = self.serversCertificatesTree.currentItem().data(0, self.CertRole) fname = ( - self.serversCertificatesTree.currentItem().text(0).replace(" ", "") + 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 = Globals.toDict( - Preferences.getSettings().value("Ssl/CaCertificatesDict")) + Preferences.getSettings().value("Ssl/CaCertificatesDict") + ) for server in certificateDict: for cert in QSslCertificate.fromData(certificateDict[server]): if cert not in caList: @@ -258,69 +262,73 @@ 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(Globals.toByteArray( - Preferences.getSettings().value("Help/SystemCertificates"))) + caList = QSslCertificate.fromData( + Globals.toByteArray( + Preferences.getSettings().value("Help/SystemCertificates") + ) + ) if not caList: caList = QSslConfiguration.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.SortOrder.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 = Utilities.decodeString( - ", ".join(cert.subjectInfo( - QSslCertificate.SubjectInfo.Organization))) + ", ".join(cert.subjectInfo(QSslCertificate.SubjectInfo.Organization)) + ) commonName = Utilities.decodeString( - ", ".join(cert.subjectInfo( - QSslCertificate.SubjectInfo.CommonName))) + ", ".join(cert.subjectInfo(QSslCertificate.SubjectInfo.CommonName)) + ) if organisation is None or organisation == "": organisation = self.tr("(Unknown)") if commonName is None or commonName == "": commonName = self.tr("(Unknown common name)") expiryDate = cert.expiryDate().toString("yyyy-MM-dd") - + # step 2: create the entry items = self.caCertificatesTree.findItems( organisation, - Qt.MatchFlag.MatchFixedString | Qt.MatchFlag.MatchCaseSensitive) + Qt.MatchFlag.MatchFixedString | Qt.MatchFlag.MatchCaseSensitive, + ) if len(items) == 0: parent = QTreeWidgetItem(self.caCertificatesTree, [organisation]) parent.setFirstColumnSpanned(True) else: parent = items[0] - + itm = QTreeWidgetItem(parent, [commonName, expiryDate]) itm.setData(0, self.CertRole, cert.toPem()) - + @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) """ @@ -328,7 +336,7 @@ self.caViewButton.setEnabled(enable) self.caDeleteButton.setEnabled(enable) self.caExportButton.setEnabled(enable) - + @pyqtSlot() def on_caViewButton_clicked(self): """ @@ -336,13 +344,15 @@ """ with contextlib.suppress(ImportError): from EricNetwork.EricSslCertificatesInfoDialog import ( - EricSslCertificatesInfoDialog + EricSslCertificatesInfoDialog, ) + cert = QSslCertificate.fromData( - self.caCertificatesTree.currentItem().data(0, self.CertRole)) + self.caCertificatesTree.currentItem().data(0, self.CertRole) + ) dlg = EricSslCertificatesInfoDialog(cert, self) dlg.exec() - + @pyqtSlot() def on_caDeleteButton_clicked(self): """ @@ -356,32 +366,33 @@ """<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))) + """ 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 its 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)) - + 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.getSettings().setValue( - "Help/SystemCertificates", pems) - + pems.append(cert.toPem() + "\n") + Preferences.getSettings().setValue("Help/SystemCertificates", pems) + # delete the certificate from the default certificates self.__updateDefaultConfiguration() - + @pyqtSlot() def on_caImportButton_clicked(self): """ @@ -393,29 +404,29 @@ for cert in certs: if cert in caCerts: commonStr = ", ".join( - cert.subjectInfo( - QSslCertificate.SubjectInfo.CommonName)) + cert.subjectInfo(QSslCertificate.SubjectInfo.CommonName) + ) EricMessageBox.warning( self, self.tr("Import Certificate"), self.tr( """<p>The certificate <b>{0}</b> already exists.""" - """ Skipping.</p>""") - .format(Utilities.decodeString(commonStr))) + """ Skipping.</p>""" + ).format(Utilities.decodeString(commonStr)), + ) else: caCerts.append(cert) - + pems = QByteArray() for cert in caCerts: - pems.append(cert.toPem() + '\n') - Preferences.getSettings().setValue( - "Help/SystemCertificates", pems) - + pems.append(cert.toPem() + "\n") + Preferences.getSettings().setValue("Help/SystemCertificates", pems) + self.caCertificatesTree.clear() self.__populateCaCertificatesTree() - + self.__updateDefaultConfiguration() - + @pyqtSlot() def on_caExportButton_clicked(self): """ @@ -423,15 +434,17 @@ """ cert = self.caCertificatesTree.currentItem().data(0, self.CertRole) fname = ( - self.caCertificatesTree.currentItem().text(0).replace(" ", "") + 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 @type str @param cert certificate to be exported encoded as PEM @@ -442,11 +455,13 @@ self, self.tr("Export Certificate"), name, - self.tr("Certificate File (PEM) (*.pem);;" - "Certificate File (DER) (*.der)"), + self.tr( + "Certificate File (PEM) (*.pem);;" "Certificate File (DER) (*.der)" + ), None, - EricFileDialog.DontConfirmOverwrite) - + EricFileDialog.DontConfirmOverwrite, + ) + if fname: fpath = pathlib.Path(fname) if not fpath.suffix: @@ -457,18 +472,22 @@ res = EricMessageBox.yesNo( self, self.tr("Export Certificate"), - self.tr("<p>The file <b>{0}</b> already exists." - " Overwrite it?</p>").format(fname), - icon=EricMessageBox.Warning) + self.tr( + "<p>The file <b>{0}</b> already exists." + " Overwrite it?</p>" + ).format(fname), + icon=EricMessageBox.Warning, + ) if not res: return - + if fpath.suffix == ".pem": crt = bytes(cert) else: crt = bytes( - QSslCertificate.fromData( - crt, QSsl.EncodingFormat.Pem)[0].toDer() + QSslCertificate.fromData(crt, QSsl.EncodingFormat.Pem)[ + 0 + ].toDer() ) try: with fpath.open("wb") as f: @@ -479,13 +498,14 @@ self.tr("Export Certificate"), self.tr( """<p>The certificate could not be written""" - """ to file <b>{0}</b></p><p>Error: {1}</p>""") - .format(fpath, str(err))) - + """ to file <b>{0}</b></p><p>Error: {1}</p>""" + ).format(fpath, str(err)), + ) + def __importCertificate(self): """ Private method to read a certificate. - + @return certificates read @rtype list of QSslCertificate """ @@ -493,19 +513,19 @@ self, self.tr("Import Certificate"), "", - self.tr("Certificate Files (*.pem *.crt *.der *.cer *.ca);;" - "All Files (*)")) - + self.tr( + "Certificate Files (*.pem *.crt *.der *.cer *.ca);;" "All Files (*)" + ), + ) + if fname: try: with pathlib.Path(fname).open("rb") as f: crt = QByteArray(f.read()) - cert = QSslCertificate.fromData( - crt, QSsl.EncodingFormat.Pem) + cert = QSslCertificate.fromData(crt, QSsl.EncodingFormat.Pem) if not cert: - cert = QSslCertificate.fromData( - crt, QSsl.EncodingFormat.Der) - + cert = QSslCertificate.fromData(crt, QSsl.EncodingFormat.Der) + return cert except OSError as err: EricMessageBox.critical( @@ -513,7 +533,8 @@ self.tr("Import Certificate"), self.tr( """<p>The certificate could not be read from file""" - """ <b>{0}</b></p><p>Error: {1}</p>""") - .format(fname, str(err))) - + """ <b>{0}</b></p><p>Error: {1}</p>""" + ).format(fname, str(err)), + ) + return []