eric7/EricNetwork/EricSslErrorHandler.py

branch
eric7
changeset 8354
12ebd3934fef
parent 8318
962bce857696
child 8356
68ec9c3d4de5
equal deleted inserted replaced
8353:799196d0b05d 8354:12ebd3934fef
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2013 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a SSL error handler.
8 """
9
10 import contextlib
11 import enum
12 import platform
13
14 from PyQt6.QtCore import QObject, QByteArray
15 from PyQt6.QtNetwork import QSslCertificate, QSslConfiguration, QSslError, QSsl
16
17 from E5Gui import E5MessageBox
18
19 import Preferences
20 import Utilities
21 import Globals
22
23
24 class EricSslErrorState(enum.Enum):
25 """
26 Class defining the SSL error handling states.
27 """
28 NOT_IGNORED = 0
29 SYSTEM_IGNORED = 1
30 USER_IGNORED = 2
31
32
33 class EricSslErrorHandler(QObject):
34 """
35 Class implementing a handler for SSL errors.
36
37 It also initializes the default SSL configuration with certificates
38 permanently accepted by the user already.
39 """
40 def __init__(self, parent=None):
41 """
42 Constructor
43
44 @param parent reference to the parent object (QObject)
45 """
46 super().__init__(parent)
47
48 caList = self.__getSystemCaCertificates()
49 if Preferences.Prefs.settings.contains("Help/CaCertificatesDict"):
50 # port old entries stored under 'Help'
51 certificateDict = Globals.toDict(
52 Preferences.Prefs.settings.value("Help/CaCertificatesDict"))
53 Preferences.Prefs.settings.setValue(
54 "Ssl/CaCertificatesDict", certificateDict)
55 Preferences.Prefs.settings.remove("Help/CaCertificatesDict")
56 else:
57 certificateDict = Globals.toDict(
58 Preferences.Prefs.settings.value("Ssl/CaCertificatesDict"))
59 for server in certificateDict:
60 for cert in QSslCertificate.fromData(certificateDict[server]):
61 if cert not in caList:
62 caList.append(cert)
63 sslCfg = QSslConfiguration.defaultConfiguration()
64 sslCfg.setCaCertificates(caList)
65 try:
66 sslProtocol = QSsl.SslProtocol.TlsV1_1OrLater
67 if Globals.isWindowsPlatform() and platform.win32_ver()[0] == '7':
68 sslProtocol = QSsl.SslProtocol.SecureProtocols
69 except AttributeError:
70 sslProtocol = QSsl.SslProtocol.SecureProtocols
71 sslCfg.setProtocol(sslProtocol)
72 with contextlib.suppress(AttributeError):
73 sslCfg.setSslOption(QSsl.SslOption.SslOptionDisableCompression,
74 True)
75 QSslConfiguration.setDefaultConfiguration(sslCfg)
76
77 def sslErrorsReplySlot(self, reply, errors):
78 """
79 Public slot to handle SSL errors for a network reply.
80
81 @param reply reference to the reply object (QNetworkReply)
82 @param errors list of SSL errors (list of QSslError)
83 """
84 self.sslErrorsReply(reply, errors)
85
86 def sslErrorsReply(self, reply, errors):
87 """
88 Public slot to handle SSL errors for a network reply.
89
90 @param reply reference to the reply object (QNetworkReply)
91 @param errors list of SSL errors (list of QSslError)
92 @return tuple indicating to ignore the SSL errors (one of NotIgnored,
93 SystemIgnored or UserIgnored) and indicating a change of the
94 default SSL configuration (boolean)
95 """
96 url = reply.url()
97 ignore, defaultChanged = self.sslErrors(errors, url.host(), url.port())
98 if ignore:
99 if defaultChanged:
100 reply.setSslConfiguration(
101 QSslConfiguration.defaultConfiguration())
102 reply.ignoreSslErrors()
103 else:
104 reply.abort()
105
106 return ignore, defaultChanged
107
108 def sslErrors(self, errors, server, port=-1):
109 """
110 Public method to handle SSL errors.
111
112 @param errors list of SSL errors
113 @type list of QSslError
114 @param server name of the server
115 @type str
116 @param port value of the port
117 @type int
118 @return tuple indicating to ignore the SSL errors and indicating a
119 change of the default SSL configuration
120 @rtype tuple of (EricSslErrorState, bool)
121 """
122 caMerge = {}
123 certificateDict = Globals.toDict(
124 Preferences.Prefs.settings.value("Ssl/CaCertificatesDict"))
125 for caServer in certificateDict:
126 caMerge[caServer] = QSslCertificate.fromData(
127 certificateDict[caServer])
128 caNew = []
129
130 errorStrings = []
131 if port != -1:
132 server += ":{0:d}".format(port)
133 if errors:
134 for err in errors:
135 if err.error() == QSslError.SslError.NoError:
136 continue
137 if server in caMerge and err.certificate() in caMerge[server]:
138 continue
139 errorStrings.append(err.errorString())
140 if not err.certificate().isNull():
141 cert = err.certificate()
142 if cert not in caNew:
143 caNew.append(cert)
144 if not errorStrings:
145 return EricSslErrorState.SYSTEM_IGNORED, False
146
147 errorString = '.</li><li>'.join(errorStrings)
148 ret = E5MessageBox.yesNo(
149 None,
150 self.tr("SSL Errors"),
151 self.tr("""<p>SSL Errors for <br /><b>{0}</b>"""
152 """<ul><li>{1}</li></ul></p>"""
153 """<p>Do you want to ignore these errors?</p>""")
154 .format(server, errorString),
155 icon=E5MessageBox.Warning)
156
157 if ret:
158 caRet = False
159 if len(caNew) > 0:
160 certinfos = []
161 for cert in caNew:
162 certinfos.append(self.__certToString(cert))
163 caRet = E5MessageBox.yesNo(
164 None,
165 self.tr("Certificates"),
166 self.tr(
167 """<p>Certificates:<br/>{0}<br/>"""
168 """Do you want to accept all these certificates?"""
169 """</p>""")
170 .format("".join(certinfos)))
171 if caRet:
172 if server not in caMerge:
173 caMerge[server] = []
174 for cert in caNew:
175 caMerge[server].append(cert)
176
177 sslCfg = QSslConfiguration.defaultConfiguration()
178 caList = sslCfg.caCertificates()
179 for cert in caNew:
180 caList.append(cert)
181 sslCfg.setCaCertificates(caList)
182 try:
183 sslCfg.setProtocol(QSsl.SslProtocol.TlsV1_1OrLater)
184 except AttributeError:
185 sslCfg.setProtocol(QSsl.SslProtocol.SecureProtocols)
186 with contextlib.suppress(AttributeError):
187 sslCfg.setSslOption(
188 QSsl.SslOption.SslOptionDisableCompression,
189 True)
190 QSslConfiguration.setDefaultConfiguration(sslCfg)
191
192 certificateDict = {}
193 for server in caMerge:
194 pems = QByteArray()
195 for cert in caMerge[server]:
196 pems.append(cert.toPem() + b'\n')
197 certificateDict[server] = pems
198 Preferences.Prefs.settings.setValue(
199 "Ssl/CaCertificatesDict",
200 certificateDict)
201
202 return EricSslErrorState.USER_IGNORED, caRet
203
204 else:
205 return EricSslErrorState.NOT_IGNORED, False
206
207 def __certToString(self, cert):
208 """
209 Private method to convert a certificate to a formatted string.
210
211 @param cert certificate to convert (QSslCertificate)
212 @return formatted string (string)
213 """
214 result = "<p>"
215
216 result += self.tr(
217 "Name: {0}"
218 ).format(
219 Utilities.html_encode(
220 Utilities.decodeString(
221 ", ".join(cert.subjectInfo(
222 QSslCertificate.SubjectInfo.CommonName))
223 )
224 )
225 )
226
227 result += self.tr(
228 "<br/>Organization: {0}"
229 ).format(
230 Utilities.html_encode(
231 Utilities.decodeString(
232 ", ".join(cert.subjectInfo(
233 QSslCertificate.SubjectInfo.Organization))
234 )
235 )
236 )
237
238 result += self.tr(
239 "<br/>Issuer: {0}"
240 ).format(
241 Utilities.html_encode(
242 Utilities.decodeString(
243 ", ".join(cert.issuerInfo(
244 QSslCertificate.SubjectInfo.CommonName))
245 )
246 )
247 )
248 result += self.tr(
249 "<br/>Not valid before: {0}<br/>Valid Until: {1}"
250 ).format(
251 Utilities.html_encode(
252 cert.effectiveDate().toString("yyyy-MM-dd")
253 ),
254 Utilities.html_encode(
255 cert.expiryDate().toString("yyyy-MM-dd")
256 )
257 )
258
259 result += "</p>"
260
261 return result
262
263 def __getSystemCaCertificates(self):
264 """
265 Private method to get the list of system certificates.
266
267 @return list of system certificates (list of QSslCertificate)
268 """
269 caList = QSslCertificate.fromData(Globals.toByteArray(
270 Preferences.Prefs.settings.value("Ssl/SystemCertificates")))
271 if not caList:
272 caList = QSslConfiguration.systemCaCertificates()
273 return caList

eric ide

mercurial