diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/sslconnection_bare.cpp	Wed Aug 13 19:35:08 2014 +0200
@@ -0,0 +1,362 @@
+/* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
+ * Software engineering by Intevation GmbH
+ *
+ * This file is Free Software under the GNU GPL (v>=2)
+ * and comes with ABSOLUTELY NO WARRANTY!
+ * See LICENSE.txt for details.
+ */
+/* TODO: Wrap ssl_session in a class for reuse.
+ * see programs/ssl/ssl_client2.c for example of session reuse */
+#include "sslconnection_bare.h"
+#include "sslhelp.h"
+
+#include <QFile>
+#include <QUuid>
+#include <QApplication>
+
+#define MAX_IO_TRIES 10
+#define MAX_RESETS 10
+
+#ifdef CONNECTION_DEBUG
+static void my_debug(void *ctx, int level, const char *str)
+{
+    fprintf((FILE *) ctx, "%s", str);
+    fflush((FILE *) ctx);
+}
+#endif
+
+SSLConnectionBare::SSLConnectionBare(const QString& url,
+                             const QByteArray& certificate):
+    SSLConnection (url, certificate)
+{
+    int ret = -1;
+
+    memset(&mSSL, 0, sizeof(ssl_context));
+    memset(&mSavedSession, 0, sizeof( ssl_session ) );
+
+    if (certificate.isEmpty()) {
+        QFile certResource(":certs/intevation.de");
+        certResource.open(QFile::ReadOnly);
+        mPinnedCert = certResource.readAll();
+        certResource.close();
+    }
+
+    ret = init();
+    if (ret == 0) {
+        mInitialized = true;
+    } else {
+        qDebug() << "Initialization error: " + getPolarSSLErrorMsg(ret);
+    }
+}
+
+int SSLConnectionBare::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;
+}
+
+SSLConnectionBare::~SSLConnectionBare() {
+    disconnect();
+    x509_crt_free(&mX509PinnedCert);
+    entropy_free(&mEntropy);
+    if (mInitialized) {
+        ssl_free(&mSSL);
+    }
+}
+
+void SSLConnectionBare::disconnect() {
+    if (mConnected) {
+        ssl_close_notify(&mSSL);
+        if (mServerFD != -1) {
+            net_close(mServerFD);
+            mServerFD = -1;
+        }
+        ssl_session_free(&mSavedSession);
+        mConnected = false;
+    }
+}
+
+int SSLConnectionBare::connect() {
+    int ret = -1;
+
+    if (!mInitialized) {
+        mErrorState = ErrUnknown;
+        return -1;
+    }
+
+    ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(),
+                  mUrl.port(443));
+
+    if (ret != 0) {
+        qDebug() << "Connect failed: " << getPolarSSLErrorMsg(ret);
+        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: " << getPolarSSLErrorMsg(ret);
+            mErrorState = SSLHandshakeFailed;
+            return ret;
+        }
+    }
+
+    ret = ssl_get_session(&mSSL, &mSavedSession);
+    if (ret != 0) {
+        qDebug() << "SSL get session failed: " << getPolarSSLErrorMsg(ret);
+
+        mErrorState = NoConnection;
+        return ret;
+    }
+    printf( " ok\n    [ Ciphersuite is %s ]\n",
+            ssl_get_ciphersuite( &mSSL) );
+    ret = validateCertificate();
+
+    if (ret == 0) {
+        mConnected = true;
+    }
+    return ret;
+}
+
+int SSLConnectionBare::validateCertificate()
+{
+    int ret = -1;
+    const x509_crt *peerCert = NULL;
+
+    /* 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 SSLConnectionBare::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();
+
+    if (mNeedsReset) {
+        ret = reset();
+        if (ret != 0) {
+            qDebug() << "Reset failed: " << getPolarSSLErrorMsg(ret);
+            return ret;
+        }
+    }
+
+    qDebug() << "Sending 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 &&
+                ret != POLARSSL_ERR_NET_WANT_READ) {
+            return ret;
+        }
+        tries++;
+        net_usleep(100000); /* sleep 100ms to give the socket a chance
+                               to clean up. */
+    } while (tries < MAX_IO_TRIES);
+
+    return ret;
+}
+
+
+int SSLConnectionBare::reset()
+{
+    int ret = -1;
+    ssl_close_notify(&mSSL);
+
+    ret = ssl_session_reset(&mSSL);
+    if (ret != 0)
+    {
+        qDebug() << "SSL Connection reset failed: "
+                 << getPolarSSLErrorMsg(ret);
+        return ret;
+    }
+
+    ssl_set_session(&mSSL, &mSavedSession);
+
+    ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(),
+                  mUrl.port(443));
+
+    if (ret != 0) {
+        mErrorState = NoConnection;
+        qDebug() << "Connection failed." << getPolarSSLErrorMsg(ret);
+        return ret;
+    }
+
+    while ((ret = ssl_handshake(&mSSL)) != 0) {
+        if (ret != POLARSSL_ERR_NET_WANT_READ &&
+                ret != POLARSSL_ERR_NET_WANT_WRITE) {
+            qDebug() << "SSL Handshake failed: "
+                 << getPolarSSLErrorMsg(ret);
+            mErrorState = SSLHandshakeFailed;
+            return ret;
+        }
+    }
+
+    qDebug() << "Reset connection. ";
+    /* Validation should not be necessary as we reused a saved
+     * session. But just to be sure. */
+    return validateCertificate();
+}
+
+QByteArray SSLConnectionBare::read(size_t len)
+{
+    unsigned char buf[len];
+    QByteArray retval("");
+    int ret = -1;
+    unsigned int tries = 0;
+
+    mNeedsReset = true;
+    do {
+        memset (buf, 0, sizeof(buf));
+        ret = ssl_read(&mSSL, buf, len);
+        if (ret == 0 ||
+            ret == POLARSSL_ERR_SSL_CONN_EOF ||
+            ret == POLARSSL_ERR_SSL_PEER_CLOSE_NOTIFY) {
+            /* EOF */
+            return retval;
+        }
+        if (ret == POLARSSL_ERR_NET_WANT_WRITE ||
+                ret == POLARSSL_ERR_NET_WANT_READ) {
+            net_usleep(100000); /* sleep 100ms to give the socket a chance
+                                   to recover */
+            tries++;
+        }
+        if (ret <= 0) {
+            qDebug() << "Read failed: " << getPolarSSLErrorMsg(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, ret);
+    } while (len > 0 && tries < MAX_IO_TRIES);
+
+    return retval;
+}
+
+

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