1 | /* |
2 | Copyright (C) 2003-2005 Justin Karneges <justin@affinix.com> |
3 | |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | of this software and associated documentation files (the "Software"), to deal |
6 | in the Software without restriction, including without limitation the rights |
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
8 | copies of the Software, and to permit persons to whom the Software is |
9 | furnished to do so, subject to the following conditions: |
10 | |
11 | The above copyright notice and this permission notice shall be included in |
12 | all copies or substantial portions of the Software. |
13 | |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
17 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN |
18 | AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
20 | */ |
21 | |
22 | #include <QtCrypto> |
23 | |
24 | #include <QCoreApplication> |
25 | #include <QTcpSocket> |
26 | |
27 | #ifdef QT_STATICPLUGIN |
28 | #include "import_plugins.h" |
29 | #endif |
30 | |
31 | char exampleCA_cert[] = |
32 | "-----BEGIN CERTIFICATE-----\n" |
33 | "MIICSzCCAbSgAwIBAgIBADANBgkqhkiG9w0BAQUFADA4MRMwEQYDVQQDEwpFeGFt\n" |
34 | "cGxlIENBMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRXhhbXBsZSBPcmcwHhcNMDYw\n" |
35 | "MzE1MDY1ODMyWhcNMDYwNDE1MDY1ODMyWjA4MRMwEQYDVQQDEwpFeGFtcGxlIENB\n" |
36 | "MQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRXhhbXBsZSBPcmcwgZ8wDQYJKoZIhvcN\n" |
37 | "AQEBBQADgY0AMIGJAoGBAL6ULdOxmpeZ+G/ypV12eNO4qnHSVIPTrYPkQuweXqPy\n" |
38 | "atwGFheG+hLVsNIh9GGOS0tCe7a3hBBKN0BJg1ppfk2x39cDx7hefYqjBuZvp/0O\n" |
39 | "8Ja3qlQiJLezITZKLxMBrsibcvcuH8zpfUdys2yaN+YGeqNfjQuoNN3Byl1TwuGJ\n" |
40 | "AgMBAAGjZTBjMB0GA1UdDgQWBBSQKCUCLNM7uKrAt5o7qv/yQm6qEzASBgNVHRMB\n" |
41 | "Af8ECDAGAQEBAgEIMB4GA1UdEQQXMBWBE2V4YW1wbGVAZXhhbXBsZS5jb20wDgYD\n" |
42 | "VR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4GBAAh+SIeT1Ao5qInw8oMSoTdO\n" |
43 | "lQ6h67ec/Jk5KmK4OoskuimmHI0Sp0C5kOCLehXbsVWW8pXsNC2fv0d2HkdaSUcX\n" |
44 | "hwLzqgyZXd4mupIYlaOTZhuHDwWPCAOZS4LVsi2tndTRHKCP12441JjNKhmZRhkR\n" |
45 | "u5zzD60nWgM9dKTaxuZM\n" |
46 | "-----END CERTIFICATE-----\n" ; |
47 | |
48 | void showCertInfo(const QCA::Certificate &cert) |
49 | { |
50 | printf(format: "-- Cert --\n" ); |
51 | printf(format: " CN: %s\n" , qPrintable(cert.commonName())); |
52 | printf(format: " Valid from: %s, until %s\n" , |
53 | qPrintable(cert.notValidBefore().toString()), |
54 | qPrintable(cert.notValidAfter().toString())); |
55 | printf(format: " PEM:\n%s\n" , qPrintable(cert.toPEM())); |
56 | } |
57 | |
58 | static QString validityToString(QCA::Validity v) |
59 | { |
60 | QString s; |
61 | switch (v) { |
62 | case QCA::ValidityGood: |
63 | s = QStringLiteral("Validated" ); |
64 | break; |
65 | case QCA::ErrorRejected: |
66 | s = QStringLiteral("Root CA is marked to reject the specified purpose" ); |
67 | break; |
68 | case QCA::ErrorUntrusted: |
69 | s = QStringLiteral("Certificate not trusted for the required purpose" ); |
70 | break; |
71 | case QCA::ErrorSignatureFailed: |
72 | s = QStringLiteral("Invalid signature" ); |
73 | break; |
74 | case QCA::ErrorInvalidCA: |
75 | s = QStringLiteral("Invalid CA certificate" ); |
76 | break; |
77 | case QCA::ErrorInvalidPurpose: |
78 | s = QStringLiteral("Invalid certificate purpose" ); |
79 | break; |
80 | case QCA::ErrorSelfSigned: |
81 | s = QStringLiteral("Certificate is self-signed" ); |
82 | break; |
83 | case QCA::ErrorRevoked: |
84 | s = QStringLiteral("Certificate has been revoked" ); |
85 | break; |
86 | case QCA::ErrorPathLengthExceeded: |
87 | s = QStringLiteral("Maximum certificate chain length exceeded" ); |
88 | break; |
89 | case QCA::ErrorExpired: |
90 | s = QStringLiteral("Certificate has expired" ); |
91 | break; |
92 | case QCA::ErrorExpiredCA: |
93 | s = QStringLiteral("CA has expired" ); |
94 | break; |
95 | case QCA::ErrorValidityUnknown: |
96 | default: |
97 | s = QStringLiteral("General certificate validation error" ); |
98 | break; |
99 | } |
100 | return s; |
101 | } |
102 | |
103 | class SecureTest : public QObject |
104 | { |
105 | Q_OBJECT |
106 | public: |
107 | SecureTest() |
108 | { |
109 | sock_done = false; |
110 | ssl_done = false; |
111 | |
112 | sock = new QTcpSocket; |
113 | connect(sender: sock, signal: &QTcpSocket::connected, context: this, slot: &SecureTest::sock_connected); |
114 | connect(sender: sock, signal: &QTcpSocket::readyRead, context: this, slot: &SecureTest::sock_readyRead); |
115 | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) |
116 | connect(sender: sock, signal: &QTcpSocket::errorOccurred, context: this, slot: &SecureTest::sock_error); |
117 | #else |
118 | connect(sock, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error), this, &SecureTest::sock_error); |
119 | #endif |
120 | |
121 | ssl = new QCA::TLS; |
122 | connect(sender: ssl, signal: &QCA::TLS::certificateRequested, context: this, slot: &SecureTest::ssl_certificateRequested); |
123 | connect(sender: ssl, signal: &QCA::TLS::handshaken, context: this, slot: &SecureTest::ssl_handshaken); |
124 | connect(sender: ssl, signal: &QCA::TLS::readyRead, context: this, slot: &SecureTest::ssl_readyRead); |
125 | connect(sender: ssl, signal: &QCA::TLS::readyReadOutgoing, context: this, slot: &SecureTest::ssl_readyReadOutgoing); |
126 | connect(sender: ssl, signal: &QCA::TLS::closed, context: this, slot: &SecureTest::ssl_closed); |
127 | connect(sender: ssl, signal: &QCA::TLS::error, context: this, slot: &SecureTest::ssl_error); |
128 | } |
129 | |
130 | ~SecureTest() override |
131 | { |
132 | delete ssl; |
133 | delete sock; |
134 | } |
135 | |
136 | void start(const QString &_host) |
137 | { |
138 | int n = _host.indexOf(c: QLatin1Char(':')); |
139 | int port; |
140 | if (n != -1) { |
141 | host = _host.mid(position: 0, n); |
142 | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) |
143 | port = QStringView(_host).mid(pos: n + 1).toInt(); |
144 | #else |
145 | port = _host.midRef(n + 1).toInt(); |
146 | #endif |
147 | } else { |
148 | host = _host; |
149 | port = 443; |
150 | } |
151 | |
152 | printf(format: "Trying %s:%d...\n" , qPrintable(host), port); |
153 | sock->connectToHost(hostName: host, port); |
154 | } |
155 | |
156 | Q_SIGNALS: |
157 | void quit(); |
158 | |
159 | private Q_SLOTS: |
160 | void sock_connected() |
161 | { |
162 | // We just do this to help doxygen... |
163 | QCA::TLS *ssl = SecureTest::ssl; |
164 | |
165 | printf(format: "Connected, starting TLS handshake...\n" ); |
166 | |
167 | QCA::CertificateCollection rootCerts = QCA::systemStore(); |
168 | |
169 | // We add this one to show how, and to make it work with |
170 | // the server example. |
171 | rootCerts.addCertificate(cert: QCA::Certificate::fromPEM(s: QString::fromLatin1(ba: exampleCA_cert))); |
172 | |
173 | if (!QCA::haveSystemStore()) |
174 | printf(format: "Warning: no root certs\n" ); |
175 | else |
176 | ssl->setTrustedCertificates(rootCerts); |
177 | |
178 | ssl->startClient(host); |
179 | } |
180 | |
181 | void sock_readyRead() |
182 | { |
183 | // We just do this to help doxygen... |
184 | QCA::TLS *ssl = SecureTest::ssl; |
185 | |
186 | ssl->writeIncoming(a: sock->readAll()); |
187 | } |
188 | |
189 | void sock_connectionClosed() |
190 | { |
191 | printf(format: "\nConnection closed.\n" ); |
192 | sock_done = true; |
193 | |
194 | if (ssl_done && sock_done) |
195 | emit quit(); |
196 | } |
197 | |
198 | void sock_error(QAbstractSocket::SocketError x) |
199 | { |
200 | if (x == QAbstractSocket::RemoteHostClosedError) { |
201 | sock_connectionClosed(); |
202 | return; |
203 | } |
204 | |
205 | printf(format: "\nSocket error.\n" ); |
206 | emit quit(); |
207 | } |
208 | |
209 | void ssl_handshaken() |
210 | { |
211 | // We just do this to help doxygen... |
212 | QCA::TLS *ssl = SecureTest::ssl; |
213 | |
214 | QCA::TLS::IdentityResult r = ssl->peerIdentityResult(); |
215 | |
216 | printf(format: "Successful SSL handshake using %s (%i of %i bits)\n" , |
217 | qPrintable(ssl->cipherSuite()), |
218 | ssl->cipherBits(), |
219 | ssl->cipherMaxBits()); |
220 | if (r != QCA::TLS::NoCertificate) { |
221 | cert = ssl->peerCertificateChain().primary(); |
222 | if (!cert.isNull()) |
223 | showCertInfo(cert); |
224 | } |
225 | |
226 | QString str = QStringLiteral("Peer Identity: " ); |
227 | if (r == QCA::TLS::Valid) |
228 | str += QStringLiteral("Valid" ); |
229 | else if (r == QCA::TLS::HostMismatch) |
230 | str += QStringLiteral("Error: Wrong certificate" ); |
231 | else if (r == QCA::TLS::InvalidCertificate) |
232 | str += QStringLiteral("Error: Invalid certificate.\n -> Reason: " ) + |
233 | validityToString(v: ssl->peerCertificateValidity()); |
234 | else |
235 | str += QStringLiteral("Error: No certificate" ); |
236 | printf(format: "%s\n" , qPrintable(str)); |
237 | |
238 | ssl->continueAfterStep(); |
239 | |
240 | printf(format: "Let's try a GET request now.\n" ); |
241 | QString req = QStringLiteral("GET / HTTP/1.0\nHost: " ) + host + QStringLiteral("\n\n" ); |
242 | ssl->write(a: req.toLatin1()); |
243 | } |
244 | |
245 | void ssl_certificateRequested() |
246 | { |
247 | // We just do this to help doxygen... |
248 | QCA::TLS *ssl = SecureTest::ssl; |
249 | |
250 | printf(format: "Server requested client certificate.\n" ); |
251 | QList<QCA::CertificateInfoOrdered> issuerList = ssl->issuerList(); |
252 | if (!issuerList.isEmpty()) { |
253 | printf(format: "Allowed issuers:\n" ); |
254 | foreach (QCA::CertificateInfoOrdered i, issuerList) |
255 | printf(format: " %s\n" , qPrintable(i.toString())); |
256 | } |
257 | |
258 | ssl->continueAfterStep(); |
259 | } |
260 | |
261 | void ssl_readyRead() |
262 | { |
263 | // We just do this to help doxygen... |
264 | QCA::TLS *ssl = SecureTest::ssl; |
265 | |
266 | QByteArray a = ssl->read(); |
267 | printf(format: "%s" , a.data()); |
268 | } |
269 | |
270 | void ssl_readyReadOutgoing() |
271 | { |
272 | // We just do this to help doxygen... |
273 | QCA::TLS *ssl = SecureTest::ssl; |
274 | |
275 | sock->write(data: ssl->readOutgoing()); |
276 | } |
277 | |
278 | void ssl_closed() |
279 | { |
280 | printf(format: "SSL session closed.\n" ); |
281 | ssl_done = true; |
282 | |
283 | if (ssl_done && sock_done) |
284 | emit quit(); |
285 | } |
286 | |
287 | void ssl_error() |
288 | { |
289 | // We just do this to help doxygen... |
290 | QCA::TLS *ssl = SecureTest::ssl; |
291 | |
292 | int x = ssl->errorCode(); |
293 | if (x == QCA::TLS::ErrorHandshake) { |
294 | printf(format: "SSL Handshake Error!\n" ); |
295 | emit quit(); |
296 | } else { |
297 | printf(format: "SSL Error!\n" ); |
298 | emit quit(); |
299 | } |
300 | } |
301 | |
302 | private: |
303 | QString host; |
304 | QTcpSocket *sock; |
305 | QCA::TLS *ssl; |
306 | QCA::Certificate cert; |
307 | bool sock_done, ssl_done; |
308 | }; |
309 | |
310 | #include "ssltest.moc" |
311 | |
312 | int main(int argc, char **argv) |
313 | { |
314 | QCA::Initializer init; |
315 | |
316 | QCoreApplication app(argc, argv); |
317 | QString host = argc > 1 ? QString::fromLocal8Bit(ba: argv[1]) : QStringLiteral("andbit.net" ); |
318 | |
319 | if (!QCA::isSupported(features: "tls" )) { |
320 | printf(format: "TLS not supported!\n" ); |
321 | return 1; |
322 | } |
323 | |
324 | SecureTest *s = new SecureTest; |
325 | QObject::connect(sender: s, signal: &SecureTest::quit, context: &app, slot: &QCoreApplication::quit); |
326 | s->start(host: host); |
327 | app.exec(); |
328 | delete s; |
329 | |
330 | return 0; |
331 | } |
332 | |