Mercurial > trustbridge
comparison ui/sslconnection_bare.cpp @ 908:d1c951b3012d
Curl based implementation of sslconnection
author | Andre Heinecke <andre.heinecke@intevation.de> |
---|---|
date | Wed, 13 Aug 2014 19:35:08 +0200 |
parents | |
children | eaed02defe6a |
comparison
equal
deleted
inserted
replaced
907:7bd75417e14e | 908:d1c951b3012d |
---|---|
1 /* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik | |
2 * Software engineering by Intevation GmbH | |
3 * | |
4 * This file is Free Software under the GNU GPL (v>=2) | |
5 * and comes with ABSOLUTELY NO WARRANTY! | |
6 * See LICENSE.txt for details. | |
7 */ | |
8 /* TODO: Wrap ssl_session in a class for reuse. | |
9 * see programs/ssl/ssl_client2.c for example of session reuse */ | |
10 #include "sslconnection_bare.h" | |
11 #include "sslhelp.h" | |
12 | |
13 #include <QFile> | |
14 #include <QUuid> | |
15 #include <QApplication> | |
16 | |
17 #define MAX_IO_TRIES 10 | |
18 #define MAX_RESETS 10 | |
19 | |
20 #ifdef CONNECTION_DEBUG | |
21 static void my_debug(void *ctx, int level, const char *str) | |
22 { | |
23 fprintf((FILE *) ctx, "%s", str); | |
24 fflush((FILE *) ctx); | |
25 } | |
26 #endif | |
27 | |
28 SSLConnectionBare::SSLConnectionBare(const QString& url, | |
29 const QByteArray& certificate): | |
30 SSLConnection (url, certificate) | |
31 { | |
32 int ret = -1; | |
33 | |
34 memset(&mSSL, 0, sizeof(ssl_context)); | |
35 memset(&mSavedSession, 0, sizeof( ssl_session ) ); | |
36 | |
37 if (certificate.isEmpty()) { | |
38 QFile certResource(":certs/intevation.de"); | |
39 certResource.open(QFile::ReadOnly); | |
40 mPinnedCert = certResource.readAll(); | |
41 certResource.close(); | |
42 } | |
43 | |
44 ret = init(); | |
45 if (ret == 0) { | |
46 mInitialized = true; | |
47 } else { | |
48 qDebug() << "Initialization error: " + getPolarSSLErrorMsg(ret); | |
49 } | |
50 } | |
51 | |
52 int SSLConnectionBare::init() | |
53 { | |
54 int ret = -1; | |
55 QUuid uuid = QUuid::createUuid(); | |
56 QString personalString = QApplication::applicationName() + uuid.toString(); | |
57 QByteArray personalBa = personalString.toLocal8Bit(); | |
58 | |
59 x509_crt_init(&mX509PinnedCert); | |
60 entropy_init(&mEntropy); | |
61 | |
62 ret = ssl_init(&mSSL); | |
63 if (ret != 0) { | |
64 /* The only documented error is malloc failed */ | |
65 mErrorState = ErrUnknown; | |
66 return ret; | |
67 } | |
68 | |
69 /* | |
70 * Initialize random generator. | |
71 * Personalisation string, does not need to be random but | |
72 * should be unique according to documentation. | |
73 * | |
74 * the ctr_drbg structure does not need to be freed explicitly. | |
75 */ | |
76 ret = ctr_drbg_init(&mCtr_drbg, entropy_func, &mEntropy, | |
77 (const unsigned char*) personalBa.constData(), | |
78 personalBa.size()); | |
79 if (ret != 0) { | |
80 ssl_free(&mSSL); | |
81 mErrorState = ErrUnknown; | |
82 return ret; | |
83 } | |
84 | |
85 ret = x509_crt_parse(&mX509PinnedCert, | |
86 (const unsigned char*) mPinnedCert.constData(), | |
87 mPinnedCert.size()); | |
88 if (ret != 0){ | |
89 ssl_free(&mSSL); | |
90 mErrorState = InvalidPinnedCertificate; | |
91 return ret; | |
92 } | |
93 | |
94 ssl_set_endpoint(&mSSL, SSL_IS_CLIENT); | |
95 ssl_set_authmode(&mSSL, SSL_VERIFY_OPTIONAL); | |
96 ssl_set_ca_chain(&mSSL, &mX509PinnedCert, NULL, NULL); | |
97 ssl_set_renegotiation(&mSSL, SSL_RENEGOTIATION_DISABLED); | |
98 ssl_set_rng(&mSSL, ctr_drbg_random, &mCtr_drbg); | |
99 #ifdef RELEASE_BUILD | |
100 ssl_set_min_version(&mSSL, SSL_MAJOR_VERSION_3, SSL_MINOR_VERSION_3); | |
101 #endif | |
102 | |
103 #ifdef CONNECTION_DEBUG | |
104 ssl_set_dbg(&mSSL, my_debug, stdout); | |
105 #endif | |
106 | |
107 return 0; | |
108 } | |
109 | |
110 SSLConnectionBare::~SSLConnectionBare() { | |
111 disconnect(); | |
112 x509_crt_free(&mX509PinnedCert); | |
113 entropy_free(&mEntropy); | |
114 if (mInitialized) { | |
115 ssl_free(&mSSL); | |
116 } | |
117 } | |
118 | |
119 void SSLConnectionBare::disconnect() { | |
120 if (mConnected) { | |
121 ssl_close_notify(&mSSL); | |
122 if (mServerFD != -1) { | |
123 net_close(mServerFD); | |
124 mServerFD = -1; | |
125 } | |
126 ssl_session_free(&mSavedSession); | |
127 mConnected = false; | |
128 } | |
129 } | |
130 | |
131 int SSLConnectionBare::connect() { | |
132 int ret = -1; | |
133 | |
134 if (!mInitialized) { | |
135 mErrorState = ErrUnknown; | |
136 return -1; | |
137 } | |
138 | |
139 ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(), | |
140 mUrl.port(443)); | |
141 | |
142 if (ret != 0) { | |
143 qDebug() << "Connect failed: " << getPolarSSLErrorMsg(ret); | |
144 mErrorState = NoConnection; | |
145 return ret; | |
146 } | |
147 | |
148 ssl_set_bio(&mSSL, net_recv, &mServerFD, | |
149 net_send, &mServerFD); | |
150 | |
151 while ((ret = ssl_handshake(&mSSL)) != 0) { | |
152 if (ret != POLARSSL_ERR_NET_WANT_READ && | |
153 ret != POLARSSL_ERR_NET_WANT_WRITE) { | |
154 qDebug() << "SSL Handshake failed: " << getPolarSSLErrorMsg(ret); | |
155 mErrorState = SSLHandshakeFailed; | |
156 return ret; | |
157 } | |
158 } | |
159 | |
160 ret = ssl_get_session(&mSSL, &mSavedSession); | |
161 if (ret != 0) { | |
162 qDebug() << "SSL get session failed: " << getPolarSSLErrorMsg(ret); | |
163 | |
164 mErrorState = NoConnection; | |
165 return ret; | |
166 } | |
167 printf( " ok\n [ Ciphersuite is %s ]\n", | |
168 ssl_get_ciphersuite( &mSSL) ); | |
169 ret = validateCertificate(); | |
170 | |
171 if (ret == 0) { | |
172 mConnected = true; | |
173 } | |
174 return ret; | |
175 } | |
176 | |
177 int SSLConnectionBare::validateCertificate() | |
178 { | |
179 int ret = -1; | |
180 const x509_crt *peerCert = NULL; | |
181 | |
182 /* we might want to set the verify function | |
183 * with ssl_set_verify before to archive the | |
184 * certificate pinning. */ | |
185 | |
186 ret = ssl_get_verify_result(&mSSL); | |
187 | |
188 if (ret != 0 ) { | |
189 if((ret & BADCERT_EXPIRED) != 0) | |
190 qDebug() << "server certificate has expired"; | |
191 if((ret & BADCERT_REVOKED) != 0) | |
192 qDebug() << "server certificate has been revoked"; | |
193 if((ret & BADCERT_CN_MISMATCH) != 0) | |
194 qDebug() << "CN mismatch"; | |
195 if((ret & BADCERT_NOT_TRUSTED) != 0) | |
196 qDebug() << "self-signed or not signed by a trusted CA"; | |
197 ret = -1; | |
198 #ifdef RELEASE_BUILD | |
199 mErrorState = InvalidCertificate; | |
200 return -1; | |
201 #endif | |
202 } | |
203 | |
204 peerCert = ssl_get_peer_cert(&mSSL); | |
205 | |
206 if (!peerCert) { | |
207 mErrorState = InvalidCertificate; | |
208 qDebug() << "Failed to get peer cert"; | |
209 return -1; | |
210 } | |
211 | |
212 if (peerCert->raw.len == 0 || | |
213 peerCert->raw.len != mX509PinnedCert.raw.len) { | |
214 mErrorState = InvalidCertificate; | |
215 qDebug() << "Certificate length mismatch"; | |
216 return -1; | |
217 } | |
218 | |
219 /* You can never be sure what those c++ operators do.. | |
220 if (mPinnedCert != QByteArray::fromRawData( | |
221 (const char*) peerCert->raw.p, | |
222 peerCert->raw.len)) { | |
223 qDebug() << "Certificate content mismatch"; | |
224 } | |
225 */ | |
226 | |
227 for (unsigned int i = 0; i < peerCert->raw.len; i++) { | |
228 if (peerCert->raw.p[i] != mX509PinnedCert.raw.p[i]) { | |
229 qDebug() << "Certificate content mismatch"; | |
230 mErrorState = InvalidCertificate; | |
231 return -1; | |
232 } | |
233 } | |
234 return 0; | |
235 } | |
236 | |
237 int SSLConnectionBare::write (const QByteArray& request) | |
238 { | |
239 unsigned int tries = 0; | |
240 int ret = -1; | |
241 | |
242 const unsigned char *buf = (const unsigned char *) request.constData(); | |
243 size_t len = (size_t) request.size(); | |
244 | |
245 if (mNeedsReset) { | |
246 ret = reset(); | |
247 if (ret != 0) { | |
248 qDebug() << "Reset failed: " << getPolarSSLErrorMsg(ret); | |
249 return ret; | |
250 } | |
251 } | |
252 | |
253 qDebug() << "Sending request: " << request; | |
254 /* According to doc for ssl_write: | |
255 * | |
256 * When this function returns POLARSSL_ERR_NET_WANT_WRITE, | |
257 * it must be called later with the same arguments, | |
258 * until it returns a positive value. | |
259 */ | |
260 do { | |
261 ret = ssl_write(&mSSL, buf, len); | |
262 if (ret >= 0) { | |
263 if ((unsigned int) ret == len) { | |
264 return 0; | |
265 } else { | |
266 qDebug() << "Write failed to write everything"; | |
267 return -1; | |
268 } | |
269 } | |
270 if (ret != POLARSSL_ERR_NET_WANT_WRITE && | |
271 ret != POLARSSL_ERR_NET_WANT_READ) { | |
272 return ret; | |
273 } | |
274 tries++; | |
275 net_usleep(100000); /* sleep 100ms to give the socket a chance | |
276 to clean up. */ | |
277 } while (tries < MAX_IO_TRIES); | |
278 | |
279 return ret; | |
280 } | |
281 | |
282 | |
283 int SSLConnectionBare::reset() | |
284 { | |
285 int ret = -1; | |
286 ssl_close_notify(&mSSL); | |
287 | |
288 ret = ssl_session_reset(&mSSL); | |
289 if (ret != 0) | |
290 { | |
291 qDebug() << "SSL Connection reset failed: " | |
292 << getPolarSSLErrorMsg(ret); | |
293 return ret; | |
294 } | |
295 | |
296 ssl_set_session(&mSSL, &mSavedSession); | |
297 | |
298 ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(), | |
299 mUrl.port(443)); | |
300 | |
301 if (ret != 0) { | |
302 mErrorState = NoConnection; | |
303 qDebug() << "Connection failed." << getPolarSSLErrorMsg(ret); | |
304 return ret; | |
305 } | |
306 | |
307 while ((ret = ssl_handshake(&mSSL)) != 0) { | |
308 if (ret != POLARSSL_ERR_NET_WANT_READ && | |
309 ret != POLARSSL_ERR_NET_WANT_WRITE) { | |
310 qDebug() << "SSL Handshake failed: " | |
311 << getPolarSSLErrorMsg(ret); | |
312 mErrorState = SSLHandshakeFailed; | |
313 return ret; | |
314 } | |
315 } | |
316 | |
317 qDebug() << "Reset connection. "; | |
318 /* Validation should not be necessary as we reused a saved | |
319 * session. But just to be sure. */ | |
320 return validateCertificate(); | |
321 } | |
322 | |
323 QByteArray SSLConnectionBare::read(size_t len) | |
324 { | |
325 unsigned char buf[len]; | |
326 QByteArray retval(""); | |
327 int ret = -1; | |
328 unsigned int tries = 0; | |
329 | |
330 mNeedsReset = true; | |
331 do { | |
332 memset (buf, 0, sizeof(buf)); | |
333 ret = ssl_read(&mSSL, buf, len); | |
334 if (ret == 0 || | |
335 ret == POLARSSL_ERR_SSL_CONN_EOF || | |
336 ret == POLARSSL_ERR_SSL_PEER_CLOSE_NOTIFY) { | |
337 /* EOF */ | |
338 return retval; | |
339 } | |
340 if (ret == POLARSSL_ERR_NET_WANT_WRITE || | |
341 ret == POLARSSL_ERR_NET_WANT_READ) { | |
342 net_usleep(100000); /* sleep 100ms to give the socket a chance | |
343 to recover */ | |
344 tries++; | |
345 } | |
346 if (ret <= 0) { | |
347 qDebug() << "Read failed: " << getPolarSSLErrorMsg(ret); | |
348 return QByteArray(); | |
349 } | |
350 if (len < (len - (unsigned int) ret)) { | |
351 /* Should never happen if ssl_read behaves */ | |
352 qDebug() << "integer overflow in polarSSLRead"; | |
353 return QByteArray(); | |
354 } | |
355 len -= (unsigned int) ret; | |
356 retval.append((const char *)buf, ret); | |
357 } while (len > 0 && tries < MAX_IO_TRIES); | |
358 | |
359 return retval; | |
360 } | |
361 | |
362 |