Mercurial > trustbridge
comparison ui/downloader.cpp @ 45:c6125d73faf4
Move SSLConnection into it's own class
author | Andre Heinecke <aheinecke@intevation.de> |
---|---|
date | Fri, 14 Mar 2014 16:40:53 +0000 |
parents | 56ba6376426e |
children | d28e2624c1d5 |
comparison
equal
deleted
inserted
replaced
44:b3e8e047bc2c | 45:c6125d73faf4 |
---|---|
6 | 6 |
7 #include <QFile> | 7 #include <QFile> |
8 #include <QDir> | 8 #include <QDir> |
9 #include <QDebug> | 9 #include <QDebug> |
10 #include <QStandardPaths> | 10 #include <QStandardPaths> |
11 #include <QUuid> | |
12 #include <QApplication> | |
13 #include <QTextStream> | 11 #include <QTextStream> |
14 #include <QLocale> | 12 #include <QLocale> |
15 #include <QSaveFile> | 13 #include <QSaveFile> |
16 | 14 |
17 #include <polarssl/net.h> | 15 #include <polarssl/net.h> |
21 #include <polarssl/error.h> | 19 #include <polarssl/error.h> |
22 #include <polarssl/certs.h> | 20 #include <polarssl/certs.h> |
23 | 21 |
24 #define MAX_SW_SIZE 10485760 | 22 #define MAX_SW_SIZE 10485760 |
25 #define MAX_LIST_SIZE 1048576 | 23 #define MAX_LIST_SIZE 1048576 |
26 #define MAX_IO_TRIES 10 | |
27 | 24 |
28 #define LIST_RESOURCE "/incoming/aheinecke/test" | 25 #define LIST_RESOURCE "/incoming/aheinecke/test" |
29 #define SW_RESOURCE "/incoming/aheinecke/test" | 26 #define SW_RESOURCE "/incoming/aheinecke/test" |
30 | 27 |
31 /* TODO: Wrap ssl_session in a class for reuse. | |
32 * see programs/ssl/ssl_client2.c for example of session reuse */ | |
33 | |
34 #ifdef CONNECTION_DEBUG | |
35 static void my_debug(void *ctx, int level, const char *str) | |
36 { | |
37 fprintf((FILE *) ctx, "%s", str); | |
38 fflush((FILE *) ctx); | |
39 } | |
40 #endif | |
41 | |
42 QString getErrorMsg(int ret) | |
43 { | |
44 char errbuf[255]; | |
45 polarssl_strerror(ret, errbuf, 255); | |
46 errbuf[254] = '\0'; /* Just to be sure */ | |
47 return QString::fromLatin1(errbuf); | |
48 } | |
49 | 28 |
50 Downloader::Downloader(QObject* parent, const QString& url, | 29 Downloader::Downloader(QObject* parent, const QString& url, |
51 const QByteArray& certificate, | 30 const QByteArray& certificate, |
52 const QDateTime& newestSW, | 31 const QDateTime& newestSW, |
53 const QDateTime& newestList): | 32 const QDateTime& newestList, |
33 const QString& resourceSW, | |
34 const QString& resourceList): | |
54 QThread(parent), | 35 QThread(parent), |
55 mUrl(url), | |
56 mPinnedCert(certificate), | |
57 mLastModSW(newestSW), | 36 mLastModSW(newestSW), |
58 mLastModList(newestList), | 37 mLastModList(newestList), |
59 mErrorState(NoError), | 38 mResourceSW(resourceSW), |
60 mInitialized(false), | 39 mResourceList(resourceList), |
61 mServerFD(-1) | 40 mSSLConnection(url, certificate) |
62 { | 41 { |
63 int ret = -1; | 42 } |
64 | 43 |
65 memset(&mSSL, 0, sizeof(ssl_context)); | |
66 | |
67 if (certificate.isEmpty()) { | |
68 QFile certResource(":certs/kolab.org"); | |
69 certResource.open(QFile::ReadOnly); | |
70 mPinnedCert = certResource.readAll(); | |
71 certResource.close(); | |
72 } | |
73 | |
74 ret = init(); | |
75 if (ret == 0) { | |
76 mInitialized = true; | |
77 } else { | |
78 qDebug() << "Initialization error: " + getErrorMsg(ret); | |
79 } | |
80 } | |
81 | |
82 int Downloader::init() | |
83 { | |
84 int ret = -1; | |
85 QUuid uuid = QUuid::createUuid(); | |
86 QString personalString = QApplication::applicationName() + uuid.toString(); | |
87 QByteArray personalBa = personalString.toLocal8Bit(); | |
88 | |
89 x509_crt_init(&mX509PinnedCert); | |
90 entropy_init(&mEntropy); | |
91 | |
92 ret = ssl_init(&mSSL); | |
93 if (ret != 0) { | |
94 /* The only documented error is malloc failed */ | |
95 mErrorState = ErrUnknown; | |
96 return ret; | |
97 } | |
98 | |
99 /* | |
100 * Initialize random generator. | |
101 * Personalisation string, does not need to be random but | |
102 * should be unique according to documentation. | |
103 * | |
104 * the ctr_drbg structure does not need to be freed explicitly. | |
105 */ | |
106 ret = ctr_drbg_init(&mCtr_drbg, entropy_func, &mEntropy, | |
107 (const unsigned char*) personalBa.constData(), | |
108 personalBa.size()); | |
109 if (ret != 0) { | |
110 ssl_free(&mSSL); | |
111 mErrorState = ErrUnknown; | |
112 return ret; | |
113 } | |
114 | |
115 ret = x509_crt_parse(&mX509PinnedCert, | |
116 (const unsigned char*) mPinnedCert.constData(), | |
117 mPinnedCert.size()); | |
118 if (ret != 0){ | |
119 ssl_free(&mSSL); | |
120 mErrorState = InvalidPinnedCertificate; | |
121 return ret; | |
122 } | |
123 | |
124 ssl_set_endpoint(&mSSL, SSL_IS_CLIENT); | |
125 ssl_set_authmode(&mSSL, SSL_VERIFY_OPTIONAL); | |
126 ssl_set_ca_chain(&mSSL, &mX509PinnedCert, NULL, NULL); | |
127 ssl_set_renegotiation(&mSSL, SSL_RENEGOTIATION_DISABLED); | |
128 ssl_set_rng(&mSSL, ctr_drbg_random, &mCtr_drbg); | |
129 #ifdef RELEASE_BUILD | |
130 ssl_set_min_version(&mSSL, SSL_MAJOR_VERSION_3, SSL_MINOR_VERSION_3); | |
131 #endif | |
132 | |
133 #ifdef CONNECTION_DEBUG | |
134 ssl_set_dbg(&mSSL, my_debug, stdout); | |
135 #endif | |
136 | |
137 return 0; | |
138 } | |
139 | 44 |
140 Downloader::~Downloader() { | 45 Downloader::~Downloader() { |
141 x509_crt_free(&mX509PinnedCert); | |
142 entropy_free(&mEntropy); | |
143 if (mInitialized) { | |
144 ssl_free(&mSSL); | |
145 } | |
146 } | 46 } |
147 | 47 |
148 QString Downloader::getDataDirectory() | 48 QString Downloader::getDataDirectory() |
149 { | 49 { |
150 QString candidate = | 50 QString candidate = |
162 qDebug() << "Could not create path to: " << candidate; | 62 qDebug() << "Could not create path to: " << candidate; |
163 return QString(); | 63 return QString(); |
164 } | 64 } |
165 } | 65 } |
166 return cDir.absolutePath(); | 66 return cDir.absolutePath(); |
167 } | |
168 | |
169 int Downloader::establishSSLConnection() { | |
170 int ret = -1; | |
171 const x509_crt *peerCert; | |
172 | |
173 if (mServerFD == -1 || !mInitialized) { | |
174 mErrorState = ErrUnknown; | |
175 return -1; | |
176 } | |
177 | |
178 ssl_set_bio(&mSSL, net_recv, &mServerFD, | |
179 net_send, &mServerFD); | |
180 | |
181 while ((ret = ssl_handshake(&mSSL)) != 0) { | |
182 if (ret != POLARSSL_ERR_NET_WANT_READ && | |
183 ret != POLARSSL_ERR_NET_WANT_WRITE) { | |
184 qDebug() << "SSL Handshake failed: " | |
185 << getErrorMsg(ret); | |
186 mErrorState = SSLHandshakeFailed; | |
187 return ret; | |
188 } | |
189 } | |
190 | |
191 /* we might want to set the verify function | |
192 * with ssl_set_verify before to archive the | |
193 * certificate pinning. */ | |
194 | |
195 ret = ssl_get_verify_result(&mSSL); | |
196 | |
197 if (ret != 0 ) { | |
198 if((ret & BADCERT_EXPIRED) != 0) | |
199 qDebug() << "server certificate has expired"; | |
200 if((ret & BADCERT_REVOKED) != 0) | |
201 qDebug() << "server certificate has been revoked"; | |
202 if((ret & BADCERT_CN_MISMATCH) != 0) | |
203 qDebug() << "CN mismatch"; | |
204 if((ret & BADCERT_NOT_TRUSTED) != 0) | |
205 qDebug() << "self-signed or not signed by a trusted CA"; | |
206 ret = -1; | |
207 #ifdef RELEASE_BUILD | |
208 mErrorState = InvalidCertificate; | |
209 return -1; | |
210 #endif | |
211 } | |
212 | |
213 peerCert = ssl_get_peer_cert(&mSSL); | |
214 | |
215 if (!peerCert) { | |
216 mErrorState = InvalidCertificate; | |
217 qDebug() << "Failed to get peer cert"; | |
218 return -1; | |
219 } | |
220 | |
221 if (peerCert->raw.len == 0 || | |
222 peerCert->raw.len != mX509PinnedCert.raw.len) { | |
223 mErrorState = InvalidCertificate; | |
224 qDebug() << "Certificate length mismatch"; | |
225 return -1; | |
226 } | |
227 | |
228 /* You can never be sure what those c++ operators do.. | |
229 if (mPinnedCert != QByteArray::fromRawData( | |
230 (const char*) peerCert->raw.p, | |
231 peerCert->raw.len)) { | |
232 qDebug() << "Certificate content mismatch"; | |
233 } | |
234 */ | |
235 | |
236 for (unsigned int i = 0; i < peerCert->raw.len; i++) { | |
237 if (peerCert->raw.p[i] != mX509PinnedCert.raw.p[i]) { | |
238 qDebug() << "Certificate content mismatch"; | |
239 mErrorState = InvalidCertificate; | |
240 return -1; | |
241 } | |
242 } | |
243 return 0; | |
244 } | |
245 | |
246 /* Helper around polarssl bare bone api */ | |
247 int polarSSLWrite (ssl_context *ssl, const QByteArray& request) | |
248 { | |
249 unsigned int tries = 0; | |
250 int ret = -1; | |
251 | |
252 const unsigned char *buf = (const unsigned char *) request.constData(); | |
253 size_t len = (size_t) request.size(); | |
254 | |
255 qDebug() << "Seinding request: " << request; | |
256 /* According to doc for ssl_write: | |
257 * | |
258 * When this function returns POLARSSL_ERR_NET_WANT_WRITE, | |
259 * it must be called later with the same arguments, | |
260 * until it returns a positive value. | |
261 */ | |
262 do { | |
263 ret = ssl_write(ssl, buf, len); | |
264 if (ret >= 0) { | |
265 if ((unsigned int) ret == len) { | |
266 return 0; | |
267 } else { | |
268 qDebug() << "Write failed to write everything"; | |
269 return -1; | |
270 } | |
271 } | |
272 | |
273 if (ret != POLARSSL_ERR_NET_WANT_WRITE) { | |
274 return ret; | |
275 } | |
276 tries++; | |
277 net_usleep(100000); /* sleep 100ms to give the socket a chance | |
278 to clean up. */ | |
279 } while (tries < MAX_IO_TRIES); | |
280 | |
281 return ret; | |
282 } | |
283 | |
284 /* Helper around polarssl bare bone api read at most len bytes | |
285 * and return them as a byte array returns a NULL byte array on error*/ | |
286 QByteArray polarSSLRead (ssl_context *ssl, size_t len) | |
287 { | |
288 unsigned char buf[len]; | |
289 QByteArray retval(""); | |
290 int ret = -1; | |
291 | |
292 do { | |
293 memset (buf, 0, sizeof(buf)); | |
294 ret = ssl_read(ssl, buf, len); | |
295 if (ret == 0 || | |
296 ret == POLARSSL_ERR_SSL_CONN_EOF) { | |
297 /* EOF */ | |
298 return retval; | |
299 } | |
300 if (ret <= 0) { | |
301 qDebug() << "Read failed: " << getErrorMsg(ret); | |
302 return QByteArray(); | |
303 } | |
304 if (len < (len - (unsigned int) ret)) { | |
305 /* Should never happen if ssl_read behaves */ | |
306 qDebug() << "integer overflow in polarSSLRead"; | |
307 return QByteArray(); | |
308 } | |
309 len -= (unsigned int) ret; | |
310 retval.append((const char *)buf, len); | |
311 } while (len > 0); | |
312 | |
313 return retval; | |
314 } | 67 } |
315 | 68 |
316 | 69 |
317 QDateTime Downloader::getLastModifiedHeader(const QString &resource) { | 70 QDateTime Downloader::getLastModifiedHeader(const QString &resource) { |
318 int ret = -1; | 71 int ret = -1; |
322 QString headRequest = | 75 QString headRequest = |
323 QString::fromLatin1("HEAD %1 HTTP/1.1\r\n" | 76 QString::fromLatin1("HEAD %1 HTTP/1.1\r\n" |
324 "Connection: Keep-Alive\r\n" | 77 "Connection: Keep-Alive\r\n" |
325 "\r\n\r\n").arg(resource); | 78 "\r\n\r\n").arg(resource); |
326 | 79 |
327 ret = polarSSLWrite (&mSSL, headRequest.toUtf8()); | 80 ret = mSSLConnection.write(headRequest.toUtf8()); |
328 if (ret != 0) { | 81 if (ret != 0) { |
329 mErrorState = ConnectionLost; | 82 emit error (tr("Connection lost"), SSLConnection::ConnectionLost); |
330 return QDateTime(); | 83 return QDateTime(); |
331 } | 84 } |
332 | 85 |
333 response = polarSSLRead(&mSSL, 1024); | 86 response = mSSLConnection.read(1024); |
334 | 87 |
335 if (response.isNull()) { | 88 if (response.isNull()) { |
336 qDebug() << "No response"; | 89 qDebug() << "No response"; |
337 mErrorState = ConnectionLost; | 90 emit error (tr("Connection lost"), SSLConnection::ConnectionLost); |
338 return QDateTime(); | 91 return QDateTime(); |
339 } | 92 } |
340 | 93 |
341 while (1) { | 94 while (1) { |
342 QString line = responseStream.readLine(); | 95 QString line = responseStream.readLine(); |
350 return candidate; | 103 return candidate; |
351 } | 104 } |
352 } | 105 } |
353 } | 106 } |
354 | 107 |
355 mErrorState = InvalidResponse; | 108 emit error (tr("Invalid response from the server"), SSLConnection::InvalidResponse); |
356 return QDateTime(); | 109 return QDateTime(); |
357 } | 110 } |
358 | 111 |
359 bool Downloader::downloadFile(const QString &resource, | 112 bool Downloader::downloadFile(const QString &resource, |
360 const QString &fileName, | 113 const QString &fileName, |
365 QString getRequest = | 118 QString getRequest = |
366 QString::fromLatin1("GET %1 HTTP/1.1\r\n\r\n").arg(resource); | 119 QString::fromLatin1("GET %1 HTTP/1.1\r\n\r\n").arg(resource); |
367 | 120 |
368 QSaveFile outputFile(fileName); | 121 QSaveFile outputFile(fileName); |
369 | 122 |
370 ret = polarSSLWrite (&mSSL, getRequest.toUtf8()); | 123 ret = mSSLConnection.write(getRequest.toUtf8()); |
371 | 124 |
372 // Open / Create the file to write to. | 125 // Open / Create the file to write to. |
373 if (!outputFile.open(QIODevice::WriteOnly)) { | 126 if (!outputFile.open(QIODevice::WriteOnly)) { |
374 qDebug() << "Failed to open file"; | 127 qDebug() << "Failed to open file"; |
375 return false; | 128 return false; |
376 } | 129 } |
377 | 130 |
378 if (ret != 0) { | 131 if (ret != 0) { |
379 mErrorState = ConnectionLost; | 132 emit error(tr("Connection lost"), SSLConnection::ConnectionLost); |
380 return false; | 133 return false; |
381 } | 134 } |
382 | 135 |
383 do { | 136 do { |
384 QByteArray response = polarSSLRead(&mSSL, 8192); | 137 QByteArray response = mSSLConnection.read(8192); |
385 if (response.isNull()) { | 138 if (response.isNull()) { |
386 qDebug() << "Error reading response"; | 139 qDebug() << "Error reading response"; |
387 mErrorState = ConnectionLost; | 140 emit error(tr("Connection lost"), SSLConnection::ConnectionLost); |
388 return false; | 141 return false; |
389 } | 142 } |
390 if (response.isEmpty()) { | 143 if (response.isEmpty()) { |
391 /* We have read everything there is to read */ | 144 /* We have read everything there is to read */ |
392 break; | 145 break; |
402 void Downloader::run() { | 155 void Downloader::run() { |
403 int ret; | 156 int ret; |
404 QDateTime remoteModList; | 157 QDateTime remoteModList; |
405 QDateTime remoteModSW; | 158 QDateTime remoteModSW; |
406 | 159 |
407 if (!mInitialized) { | 160 if (!mSSLConnection.initialized()) { |
408 emit error(tr("Failed to initialize SSL Module."), ErrUnknown); | 161 emit error(tr("Failed to initialize SSL Module."), SSLConnection::ErrUnknown); |
409 return; | 162 return; |
410 } | 163 } |
411 | 164 |
412 ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(), | 165 ret = mSSLConnection.connect(); |
413 mUrl.port(443)); | |
414 | 166 |
415 if (ret != 0) { | 167 if (ret != 0) { |
416 mErrorState = NoConnection; | 168 emit error(tr("Failed to connect."), |
417 emit error(tr("Failed to connect to %1.").arg(mUrl.host()), | 169 mSSLConnection.getLastError()); |
418 mErrorState); | |
419 return; | 170 return; |
420 } | 171 } |
421 | 172 |
422 emit progress(tr("Connected"), 1, -1); | 173 emit progress(tr("Connected"), 1, -1); |
423 | 174 |
424 ret = establishSSLConnection(); | 175 remoteModSW = getLastModifiedHeader(mResourceSW); |
425 if (ret != 0) { | 176 remoteModList = getLastModifiedHeader(mResourceList); |
426 qDebug() << "SSL conncetion failed: " << getErrorMsg(ret); | |
427 emit error(tr("Failed to connect to %1.").arg(mUrl.host()), | |
428 mErrorState); | |
429 return; | |
430 } | |
431 | |
432 remoteModSW = getLastModifiedHeader(QString::fromLatin1("/incoming/aheinecke/test")); | |
433 remoteModList = getLastModifiedHeader(QString::fromLatin1("/incoming/aheinecke/test")); | |
434 | 177 |
435 if (!remoteModSW.isValid() || !remoteModList.isValid()) { | 178 if (!remoteModSW.isValid() || !remoteModList.isValid()) { |
436 qDebug() << "Could not read headers"; | 179 qDebug() << "Could not read headers"; |
437 return; | 180 return; |
438 } | 181 } |
478 | 221 |
479 emit newListAvailable(fileName, remoteModList); | 222 emit newListAvailable(fileName, remoteModList); |
480 } | 223 } |
481 | 224 |
482 emit progress(tr("Closing"), 1, -1); | 225 emit progress(tr("Closing"), 1, -1); |
483 ssl_close_notify (&mSSL); | 226 } |
484 | |
485 if (mServerFD != -1) { | |
486 net_close(mServerFD); | |
487 mServerFD = -1; | |
488 } | |
489 } |