1# -*- coding: utf-8 -*-
2
3# Copyright (c) 2010 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4#
5
6"""
7Module implementing a widget to show SSL information.
8"""
9
10from PyQt5.QtCore import Qt, QUrl, QPoint
11from PyQt5.QtWidgets import QMenu, QGridLayout, QLabel, QSizePolicy
12from PyQt5.QtNetwork import QSsl, QSslConfiguration, QSslCertificate
13
14import UI.PixmapCache
15import Utilities
16
17
18class E5SslInfoWidget(QMenu):
19    """
20    Class implementing a widget to show SSL certificate infos.
21    """
22    def __init__(self, url, configuration, parent=None):
23        """
24        Constructor
25
26        @param url URL to show SSL info for (QUrl)
27        @param configuration SSL configuration (QSslConfiguration)
28        @param parent reference to the parent widget (QWidget)
29        """
30        super().__init__(parent)
31
32        self.__url = QUrl(url)
33        self.__configuration = QSslConfiguration(configuration)
34
35        self.setMinimumWidth(400)
36
37        certList = self.__configuration.peerCertificateChain()
38        cert = certList[0] if certList else QSslCertificate()
39
40        layout = QGridLayout(self)
41        rows = 0
42
43        ##########################################
44        ## Identity Information
45        ##########################################
46        imageLabel = QLabel(self)
47        layout.addWidget(imageLabel, rows, 0, Qt.AlignmentFlag.AlignCenter)
48
49        label = QLabel(self)
50        label.setWordWrap(True)
51        label.setSizePolicy(QSizePolicy.Policy.Expanding,
52                            QSizePolicy.Policy.Preferred)
53        label.setText(self.tr("Identity"))
54        font = label.font()
55        font.setBold(True)
56        label.setFont(font)
57        layout.addWidget(label, rows, 1)
58        rows += 1
59
60        label = QLabel(self)
61        label.setWordWrap(True)
62        if cert.isNull():
63            label.setText(self.tr(
64                "Warning: this site is NOT carrying a certificate."))
65            imageLabel.setPixmap(UI.PixmapCache.getPixmap("securityLow32"))
66        else:
67            valid = not cert.isBlacklisted()
68            if valid:
69                txt = ", ".join(
70                    cert.issuerInfo(QSslCertificate.SubjectInfo.CommonName))
71                label.setText(self.tr(
72                    "The certificate for this site is valid"
73                    " and has been verified by:\n{0}").format(
74                    Utilities.decodeString(txt)))
75                imageLabel.setPixmap(
76                    UI.PixmapCache.getPixmap("securityHigh32"))
77            else:
78                label.setText(self.tr(
79                    "The certificate for this site is NOT valid."))
80                imageLabel.setPixmap(
81                    UI.PixmapCache.getPixmap("securityLow32"))
82            layout.addWidget(label, rows, 1)
83            rows += 1
84
85            label = QLabel(self)
86            label.setWordWrap(True)
87            label.setText(
88                '<a href="moresslinfos">' +
89                self.tr("Certificate Information") + "</a>")
90            label.linkActivated.connect(self.__showCertificateInfos)
91            layout.addWidget(label, rows, 1)
92            rows += 1
93
94        ##########################################
95        ## Identity Information
96        ##########################################
97        imageLabel = QLabel(self)
98        layout.addWidget(imageLabel, rows, 0, Qt.AlignmentFlag.AlignCenter)
99
100        label = QLabel(self)
101        label.setWordWrap(True)
102        label.setText(self.tr("Encryption"))
103        font = label.font()
104        font.setBold(True)
105        label.setFont(font)
106        layout.addWidget(label, rows, 1)
107        rows += 1
108
109        cipher = self.__configuration.sessionCipher()
110        if cipher.isNull():
111            label = QLabel(self)
112            label.setWordWrap(True)
113            label.setText(self.tr(
114                'Your connection to "{0}" is NOT encrypted.\n').format(
115                self.__url.host()))
116            layout.addWidget(label, rows, 1)
117            imageLabel.setPixmap(UI.PixmapCache.getPixmap("securityLow32"))
118            rows += 1
119        else:
120            label = QLabel(self)
121            label.setWordWrap(True)
122            label.setText(self.tr(
123                'Your connection to "{0}" is encrypted.').format(
124                self.__url.host()))
125            layout.addWidget(label, rows, 1)
126
127            proto = cipher.protocol()
128            if proto == QSsl.SslProtocol.SslV3:
129                sslVersion = "SSL 3.0"
130                imageLabel.setPixmap(
131                    UI.PixmapCache.getPixmap("securityLow32"))
132            elif proto == QSsl.SslProtocol.TlsV1SslV3:
133                sslVersion = "TLS 1.0/SSL 3.0"
134                imageLabel.setPixmap(
135                    UI.PixmapCache.getPixmap("securityLow32"))
136            elif proto == QSsl.SslProtocol.SslV2:
137                sslVersion = "SSL 2.0"
138                imageLabel.setPixmap(
139                    UI.PixmapCache.getPixmap("securityLow32"))
140            else:
141                sslVersion = self.tr("unknown")
142                imageLabel.setPixmap(
143                    UI.PixmapCache.getPixmap("securityLow32"))
144            if proto == QSsl.SslProtocol.TlsV1_0:
145                sslVersion = "TLS 1.0"
146                imageLabel.setPixmap(
147                    UI.PixmapCache.getPixmap("securityHigh32"))
148            elif proto == QSsl.SslProtocol.TlsV1_1:
149                sslVersion = "TLS 1.1"
150                imageLabel.setPixmap(
151                    UI.PixmapCache.getPixmap("securityHigh32"))
152            elif proto == QSsl.SslProtocol.TlsV1_2:
153                sslVersion = "TLS 1.2"
154                imageLabel.setPixmap(
155                    UI.PixmapCache.getPixmap("securityHigh32"))
156            elif proto == QSsl.SslProtocol.TlsV1_3:
157                sslVersion = "TLS 1.3"
158                imageLabel.setPixmap(
159                    UI.PixmapCache.getPixmap("securityHigh32"))
160            rows += 1
161
162            label = QLabel(self)
163            label.setWordWrap(True)
164            label.setText(self.tr(
165                "It uses protocol: {0}").format(sslVersion))
166            layout.addWidget(label, rows, 1)
167            rows += 1
168
169            label = QLabel(self)
170            label.setWordWrap(True)
171            label.setText(self.tr(
172                "It is encrypted using {0} at {1} bits, "
173                "with {2} for message authentication and "
174                "{3} as key exchange mechanism.\n\n").format(
175                cipher.encryptionMethod(),
176                cipher.usedBits(),
177                cipher.authenticationMethod(),
178                cipher.keyExchangeMethod()))
179            layout.addWidget(label, rows, 1)
180            rows += 1
181
182    def showAt(self, pos):
183        """
184        Public method to show the widget.
185
186        @param pos position to show at (QPoint)
187        """
188        self.adjustSize()
189        xpos = pos.x() - self.width()
190        if xpos < 0:
191            xpos = 10
192        p = QPoint(xpos, pos.y() + 10)
193        self.move(p)
194        self.show()
195
196    def __showCertificateInfos(self):
197        """
198        Private slot to show certificate information.
199        """
200        from .E5SslCertificatesInfoDialog import E5SslCertificatesInfoDialog
201        dlg = E5SslCertificatesInfoDialog(
202            self.__configuration.peerCertificateChain())
203        dlg.exec()
204
205    def accept(self):
206        """
207        Public method to accept the widget.
208        """
209        self.close()
210