Mercurial > trustbridge
diff ui/sslconnection.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 | |
children | d28e2624c1d5 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ui/sslconnection.cpp Fri Mar 14 16:40:53 2014 +0000 @@ -0,0 +1,279 @@ +/* TODO: Wrap ssl_session in a class for reuse. + * see programs/ssl/ssl_client2.c for example of session reuse */ +#include "sslconnection.h" + +#include <QFile> +#include <QUuid> +#include <QApplication> + +#define MAX_IO_TRIES 10 + +#ifdef CONNECTION_DEBUG +static void my_debug(void *ctx, int level, const char *str) +{ + fprintf((FILE *) ctx, "%s", str); + fflush((FILE *) ctx); +} +#endif + +QString getErrorMsg(int ret) +{ + char errbuf[255]; + polarssl_strerror(ret, errbuf, 255); + errbuf[254] = '\0'; /* Just to be sure */ + return QString::fromLatin1(errbuf); +} + +SSLConnection::SSLConnection(const QString& url, + const QByteArray& certificate): + mUrl(url), + mPinnedCert(certificate), + mInitialized(false), + mConnected(false), + mServerFD(-1), + mErrorState(NoError) +{ + int ret = -1; + + memset(&mSSL, 0, sizeof(ssl_context)); + + if (certificate.isEmpty()) { + QFile certResource(":certs/kolab.org"); + certResource.open(QFile::ReadOnly); + mPinnedCert = certResource.readAll(); + certResource.close(); + } + + ret = init(); + if (ret == 0) { + mInitialized = true; + } else { + qDebug() << "Initialization error: " + getErrorMsg(ret); + } +} + +int SSLConnection::init() +{ + int ret = -1; + QUuid uuid = QUuid::createUuid(); + QString personalString = QApplication::applicationName() + uuid.toString(); + QByteArray personalBa = personalString.toLocal8Bit(); + + x509_crt_init(&mX509PinnedCert); + entropy_init(&mEntropy); + + ret = ssl_init(&mSSL); + if (ret != 0) { + /* The only documented error is malloc failed */ + mErrorState = ErrUnknown; + return ret; + } + + /* + * Initialize random generator. + * Personalisation string, does not need to be random but + * should be unique according to documentation. + * + * the ctr_drbg structure does not need to be freed explicitly. + */ + ret = ctr_drbg_init(&mCtr_drbg, entropy_func, &mEntropy, + (const unsigned char*) personalBa.constData(), + personalBa.size()); + if (ret != 0) { + ssl_free(&mSSL); + mErrorState = ErrUnknown; + return ret; + } + + ret = x509_crt_parse(&mX509PinnedCert, + (const unsigned char*) mPinnedCert.constData(), + mPinnedCert.size()); + if (ret != 0){ + ssl_free(&mSSL); + mErrorState = InvalidPinnedCertificate; + return ret; + } + + ssl_set_endpoint(&mSSL, SSL_IS_CLIENT); + ssl_set_authmode(&mSSL, SSL_VERIFY_OPTIONAL); + ssl_set_ca_chain(&mSSL, &mX509PinnedCert, NULL, NULL); + ssl_set_renegotiation(&mSSL, SSL_RENEGOTIATION_DISABLED); + ssl_set_rng(&mSSL, ctr_drbg_random, &mCtr_drbg); +#ifdef RELEASE_BUILD + ssl_set_min_version(&mSSL, SSL_MAJOR_VERSION_3, SSL_MINOR_VERSION_3); +#endif + +#ifdef CONNECTION_DEBUG + ssl_set_dbg(&mSSL, my_debug, stdout); +#endif + + return 0; +} + +SSLConnection::~SSLConnection() { + if (mConnected) { + ssl_close_notify (&mSSL); + if (mServerFD != -1) { + net_close(mServerFD); + mServerFD = -1; + } + } + x509_crt_free(&mX509PinnedCert); + entropy_free(&mEntropy); + if (mInitialized) { + ssl_free(&mSSL); + } +} + +int SSLConnection::connect() { + int ret = -1; + const x509_crt *peerCert; + + if (!mInitialized) { + mErrorState = ErrUnknown; + return -1; + } + + ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(), + mUrl.port(443)); + + if (ret != 0) { + mErrorState = NoConnection; + return ret; + } + + ssl_set_bio(&mSSL, net_recv, &mServerFD, + net_send, &mServerFD); + + while ((ret = ssl_handshake(&mSSL)) != 0) { + if (ret != POLARSSL_ERR_NET_WANT_READ && + ret != POLARSSL_ERR_NET_WANT_WRITE) { + qDebug() << "SSL Handshake failed: " + << getErrorMsg(ret); + mErrorState = SSLHandshakeFailed; + return ret; + } + } + + /* we might want to set the verify function + * with ssl_set_verify before to archive the + * certificate pinning. */ + + ret = ssl_get_verify_result(&mSSL); + + if (ret != 0 ) { + if((ret & BADCERT_EXPIRED) != 0) + qDebug() << "server certificate has expired"; + if((ret & BADCERT_REVOKED) != 0) + qDebug() << "server certificate has been revoked"; + if((ret & BADCERT_CN_MISMATCH) != 0) + qDebug() << "CN mismatch"; + if((ret & BADCERT_NOT_TRUSTED) != 0) + qDebug() << "self-signed or not signed by a trusted CA"; + ret = -1; +#ifdef RELEASE_BUILD + mErrorState = InvalidCertificate; + return -1; +#endif + } + + peerCert = ssl_get_peer_cert(&mSSL); + + if (!peerCert) { + mErrorState = InvalidCertificate; + qDebug() << "Failed to get peer cert"; + return -1; + } + + if (peerCert->raw.len == 0 || + peerCert->raw.len != mX509PinnedCert.raw.len) { + mErrorState = InvalidCertificate; + qDebug() << "Certificate length mismatch"; + return -1; + } + + /* You can never be sure what those c++ operators do.. + if (mPinnedCert != QByteArray::fromRawData( + (const char*) peerCert->raw.p, + peerCert->raw.len)) { + qDebug() << "Certificate content mismatch"; + } + */ + + for (unsigned int i = 0; i < peerCert->raw.len; i++) { + if (peerCert->raw.p[i] != mX509PinnedCert.raw.p[i]) { + qDebug() << "Certificate content mismatch"; + mErrorState = InvalidCertificate; + return -1; + } + } + return 0; +} + +int SSLConnection::write (const QByteArray& request) +{ + unsigned int tries = 0; + int ret = -1; + + const unsigned char *buf = (const unsigned char *) request.constData(); + size_t len = (size_t) request.size(); + + qDebug() << "Seinding request: " << request; + /* According to doc for ssl_write: + * + * When this function returns POLARSSL_ERR_NET_WANT_WRITE, + * it must be called later with the same arguments, + * until it returns a positive value. + */ + do { + ret = ssl_write(&mSSL, buf, len); + if (ret >= 0) { + if ((unsigned int) ret == len) { + return 0; + } else { + qDebug() << "Write failed to write everything"; + return -1; + } + } + + if (ret != POLARSSL_ERR_NET_WANT_WRITE) { + return ret; + } + tries++; + net_usleep(100000); /* sleep 100ms to give the socket a chance + to clean up. */ + } while (tries < MAX_IO_TRIES); + + return ret; +} + +QByteArray SSLConnection::read(size_t len) +{ + unsigned char buf[len]; + QByteArray retval(""); + int ret = -1; + + do { + memset (buf, 0, sizeof(buf)); + ret = ssl_read(&mSSL, buf, len); + if (ret == 0 || + ret == POLARSSL_ERR_SSL_CONN_EOF) { + /* EOF */ + return retval; + } + if (ret <= 0) { + qDebug() << "Read failed: " << getErrorMsg(ret); + return QByteArray(); + } + if (len < (len - (unsigned int) ret)) { + /* Should never happen if ssl_read behaves */ + qDebug() << "integer overflow in polarSSLRead"; + return QByteArray(); + } + len -= (unsigned int) ret; + retval.append((const char *)buf, len); + } while (len > 0); + + return retval; +} +