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 }

http://wald.intevation.org/projects/trustbridge/