aheinecke@404: /* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik
aheinecke@404:  * Software engineering by Intevation GmbH
aheinecke@404:  *
aheinecke@404:  * This file is Free Software under the GNU GPL (v>=2)
aheinecke@404:  * and comes with ABSOLUTELY NO WARRANTY!
aheinecke@404:  * See LICENSE.txt for details.
aheinecke@404:  */
aheinecke@45: /* TODO: Wrap ssl_session in a class for reuse.
aheinecke@45:  * see programs/ssl/ssl_client2.c for example of session reuse */
aheinecke@45: #include "sslconnection.h"
aheinecke@452: #include "sslhelp.h"
aheinecke@45: 
aheinecke@45: #include <QFile>
aheinecke@45: #include <QUuid>
aheinecke@45: #include <QApplication>
aheinecke@45: 
aheinecke@45: #define MAX_IO_TRIES 10
aheinecke@46: #define MAX_RESETS 10
aheinecke@45: 
aheinecke@45: #ifdef CONNECTION_DEBUG
aheinecke@45: static void my_debug(void *ctx, int level, const char *str)
aheinecke@45: {
aheinecke@45:     fprintf((FILE *) ctx, "%s", str);
aheinecke@45:     fflush((FILE *) ctx);
aheinecke@45: }
aheinecke@45: #endif
aheinecke@45: 
aheinecke@45: SSLConnection::SSLConnection(const QString& url,
aheinecke@45:                              const QByteArray& certificate):
aheinecke@45:     mUrl(url),
aheinecke@45:     mPinnedCert(certificate),
aheinecke@45:     mInitialized(false),
aheinecke@45:     mConnected(false),
bernhard@574:     mNeedsReset(false),
aheinecke@45:     mServerFD(-1),
aheinecke@45:     mErrorState(NoError)
aheinecke@45: {
aheinecke@45:     int ret = -1;
aheinecke@45: 
aheinecke@45:     memset(&mSSL, 0, sizeof(ssl_context));
aheinecke@46:     memset(&mSavedSession, 0, sizeof( ssl_session ) );
aheinecke@45: 
aheinecke@45:     if (certificate.isEmpty()) {
aheinecke@409:         QFile certResource(":certs/intevation.de");
aheinecke@45:         certResource.open(QFile::ReadOnly);
aheinecke@45:         mPinnedCert = certResource.readAll();
aheinecke@45:         certResource.close();
aheinecke@45:     }
aheinecke@45: 
aheinecke@45:     ret = init();
aheinecke@45:     if (ret == 0) {
aheinecke@45:         mInitialized = true;
aheinecke@45:     } else {
aheinecke@452:         qDebug() << "Initialization error: " + getPolarSSLErrorMsg(ret);
aheinecke@45:     }
aheinecke@45: }
aheinecke@45: 
aheinecke@45: int SSLConnection::init()
aheinecke@45: {
aheinecke@45:     int ret = -1;
aheinecke@45:     QUuid uuid = QUuid::createUuid();
aheinecke@45:     QString personalString = QApplication::applicationName() + uuid.toString();
aheinecke@45:     QByteArray personalBa = personalString.toLocal8Bit();
aheinecke@45: 
aheinecke@45:     x509_crt_init(&mX509PinnedCert);
aheinecke@45:     entropy_init(&mEntropy);
aheinecke@45: 
aheinecke@45:     ret = ssl_init(&mSSL);
aheinecke@45:     if (ret != 0) {
aheinecke@45:         /* The only documented error is malloc failed */
aheinecke@45:         mErrorState = ErrUnknown;
aheinecke@45:         return ret;
aheinecke@45:     }
aheinecke@45: 
aheinecke@45:     /*
aheinecke@45:      * Initialize random generator.
aheinecke@45:      * Personalisation string, does not need to be random but
aheinecke@45:      * should be unique according to documentation.
aheinecke@45:      *
aheinecke@45:      * the ctr_drbg structure does not need to be freed explicitly.
aheinecke@45:      */
aheinecke@45:     ret = ctr_drbg_init(&mCtr_drbg, entropy_func, &mEntropy,
aheinecke@45:                         (const unsigned char*) personalBa.constData(),
aheinecke@45:                         personalBa.size());
aheinecke@45:     if (ret != 0) {
aheinecke@45:         ssl_free(&mSSL);
aheinecke@45:         mErrorState = ErrUnknown;
aheinecke@45:         return ret;
aheinecke@45:     }
aheinecke@45: 
aheinecke@45:     ret = x509_crt_parse(&mX509PinnedCert,
aheinecke@45:                          (const unsigned char*) mPinnedCert.constData(),
aheinecke@45:                          mPinnedCert.size());
aheinecke@45:     if (ret != 0){
aheinecke@45:         ssl_free(&mSSL);
aheinecke@45:         mErrorState = InvalidPinnedCertificate;
aheinecke@45:         return ret;
aheinecke@45:     }
aheinecke@45: 
aheinecke@45:     ssl_set_endpoint(&mSSL, SSL_IS_CLIENT);
aheinecke@45:     ssl_set_authmode(&mSSL, SSL_VERIFY_OPTIONAL);
aheinecke@45:     ssl_set_ca_chain(&mSSL, &mX509PinnedCert, NULL, NULL);
aheinecke@45:     ssl_set_renegotiation(&mSSL, SSL_RENEGOTIATION_DISABLED);
aheinecke@45:     ssl_set_rng(&mSSL, ctr_drbg_random, &mCtr_drbg);
aheinecke@45: #ifdef RELEASE_BUILD
aheinecke@45:     ssl_set_min_version(&mSSL, SSL_MAJOR_VERSION_3, SSL_MINOR_VERSION_3);
aheinecke@45: #endif
aheinecke@45: 
aheinecke@45: #ifdef CONNECTION_DEBUG
aheinecke@45:     ssl_set_dbg(&mSSL, my_debug, stdout);
aheinecke@45: #endif
aheinecke@45: 
aheinecke@45:     return 0;
aheinecke@45: }
aheinecke@45: 
aheinecke@45: SSLConnection::~SSLConnection() {
aheinecke@46:     disconnect();
aheinecke@45:     x509_crt_free(&mX509PinnedCert);
aheinecke@45:     entropy_free(&mEntropy);
aheinecke@45:     if (mInitialized) {
aheinecke@45:         ssl_free(&mSSL);
aheinecke@45:     }
aheinecke@45: }
aheinecke@45: 
aheinecke@46: void SSLConnection::disconnect() {
aheinecke@46:     if (mConnected) {
aheinecke@46:         ssl_close_notify(&mSSL);
aheinecke@46:         if (mServerFD != -1) {
aheinecke@46:             net_close(mServerFD);
aheinecke@46:             mServerFD = -1;
aheinecke@46:         }
aheinecke@46:         ssl_session_free(&mSavedSession);
aheinecke@46:         mConnected = false;
aheinecke@46:     }
aheinecke@46: }
aheinecke@46: 
aheinecke@45: int SSLConnection::connect() {
aheinecke@45:     int ret = -1;
aheinecke@45: 
aheinecke@45:     if (!mInitialized) {
aheinecke@45:         mErrorState = ErrUnknown;
aheinecke@45:         return -1;
aheinecke@45:     }
aheinecke@45: 
aheinecke@45:     ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(),
aheinecke@45:                   mUrl.port(443));
aheinecke@45: 
aheinecke@45:     if (ret != 0) {
aheinecke@452:         qDebug() << "Connect failed: " << getPolarSSLErrorMsg(ret);
aheinecke@45:         mErrorState = NoConnection;
aheinecke@45:         return ret;
aheinecke@45:     }
aheinecke@45: 
aheinecke@45:     ssl_set_bio(&mSSL, net_recv, &mServerFD,
aheinecke@45:                        net_send, &mServerFD);
aheinecke@45: 
aheinecke@45:     while ((ret = ssl_handshake(&mSSL)) != 0) {
aheinecke@45:         if (ret != POLARSSL_ERR_NET_WANT_READ &&
aheinecke@45:                 ret != POLARSSL_ERR_NET_WANT_WRITE) {
aheinecke@452:             qDebug() << "SSL Handshake failed: " << getPolarSSLErrorMsg(ret);
aheinecke@45:             mErrorState = SSLHandshakeFailed;
aheinecke@45:             return ret;
aheinecke@45:         }
aheinecke@45:     }
aheinecke@45: 
aheinecke@46:     ret = ssl_get_session(&mSSL, &mSavedSession);
aheinecke@46:     if (ret != 0) {
aheinecke@452:         qDebug() << "SSL get session failed: " << getPolarSSLErrorMsg(ret);
aheinecke@46: 
aheinecke@46:         mErrorState = NoConnection;
aheinecke@46:         return ret;
aheinecke@46:     }
aheinecke@46:     printf( " ok\n    [ Ciphersuite is %s ]\n",
aheinecke@46:             ssl_get_ciphersuite( &mSSL) );
aheinecke@46:     ret = validateCertificate();
aheinecke@46: 
aheinecke@46:     if (ret == 0) {
aheinecke@46:         mConnected = true;
aheinecke@46:     }
aheinecke@46:     return ret;
aheinecke@46: }
aheinecke@46: 
aheinecke@46: int SSLConnection::validateCertificate()
aheinecke@46: {
aheinecke@46:     int ret = -1;
aheinecke@46:     const x509_crt *peerCert = NULL;
aheinecke@46: 
aheinecke@45:     /* we might want to set the verify function
aheinecke@45:      * with ssl_set_verify before to archive the
aheinecke@45:      * certificate pinning. */
aheinecke@45: 
aheinecke@45:     ret = ssl_get_verify_result(&mSSL);
aheinecke@45: 
aheinecke@45:     if (ret != 0 ) {
aheinecke@45:         if((ret & BADCERT_EXPIRED) != 0)
aheinecke@45:            qDebug() << "server certificate has expired";
aheinecke@45:         if((ret & BADCERT_REVOKED) != 0)
aheinecke@45:            qDebug() << "server certificate has been revoked";
aheinecke@45:         if((ret & BADCERT_CN_MISMATCH) != 0)
aheinecke@45:            qDebug() << "CN mismatch";
aheinecke@45:         if((ret & BADCERT_NOT_TRUSTED) != 0)
aheinecke@45:             qDebug() << "self-signed or not signed by a trusted CA";
aheinecke@45:         ret = -1;
aheinecke@45: #ifdef RELEASE_BUILD
aheinecke@45:         mErrorState = InvalidCertificate;
aheinecke@45:         return -1;
aheinecke@45: #endif
aheinecke@45:     }
aheinecke@45: 
aheinecke@45:     peerCert = ssl_get_peer_cert(&mSSL);
aheinecke@45: 
aheinecke@45:     if (!peerCert) {
aheinecke@45:         mErrorState = InvalidCertificate;
aheinecke@45:         qDebug() << "Failed to get peer cert";
aheinecke@45:         return -1;
aheinecke@45:     }
aheinecke@45: 
aheinecke@45:     if (peerCert->raw.len == 0 ||
aheinecke@45:         peerCert->raw.len != mX509PinnedCert.raw.len) {
aheinecke@45:         mErrorState = InvalidCertificate;
aheinecke@45:         qDebug() << "Certificate length mismatch";
aheinecke@45:         return -1;
aheinecke@45:     }
aheinecke@45: 
aheinecke@45:     /* You can never be sure what those c++ operators do..
aheinecke@45:     if (mPinnedCert != QByteArray::fromRawData(
aheinecke@45:                 (const char*) peerCert->raw.p,
aheinecke@45:             peerCert->raw.len)) {
aheinecke@45:         qDebug() << "Certificate content mismatch";
aheinecke@45:     }
aheinecke@45:     */
aheinecke@45: 
aheinecke@45:     for (unsigned int i = 0; i < peerCert->raw.len; i++) {
aheinecke@45:         if (peerCert->raw.p[i] != mX509PinnedCert.raw.p[i]) {
aheinecke@45:             qDebug() << "Certificate content mismatch";
aheinecke@45:             mErrorState = InvalidCertificate;
aheinecke@45:             return -1;
aheinecke@45:         }
aheinecke@45:     }
aheinecke@45:     return 0;
aheinecke@45: }
aheinecke@45: 
aheinecke@45: int SSLConnection::write (const QByteArray& request)
aheinecke@45: {
aheinecke@45:     unsigned int tries = 0;
aheinecke@45:     int ret = -1;
aheinecke@45: 
aheinecke@45:     const unsigned char *buf = (const unsigned char *) request.constData();
aheinecke@45:     size_t len = (size_t) request.size();
aheinecke@45: 
aheinecke@46:     if (mNeedsReset) {
aheinecke@46:         ret = reset();
aheinecke@46:         if (ret != 0) {
aheinecke@452:             qDebug() << "Reset failed: " << getPolarSSLErrorMsg(ret);
aheinecke@46:             return ret;
aheinecke@46:         }
aheinecke@46:     }
aheinecke@46: 
aheinecke@46:     qDebug() << "Sending request: " << request;
aheinecke@45:     /* According to doc for ssl_write:
aheinecke@45:      *
aheinecke@45:      * When this function returns POLARSSL_ERR_NET_WANT_WRITE,
aheinecke@45:      * it must be called later with the same arguments,
aheinecke@45:      * until it returns a positive value.
aheinecke@45:      */
aheinecke@45:     do {
aheinecke@45:         ret = ssl_write(&mSSL, buf, len);
aheinecke@45:         if (ret >= 0) {
aheinecke@45:             if ((unsigned int) ret == len) {
aheinecke@45:                 return 0;
aheinecke@45:             } else {
aheinecke@45:                 qDebug() << "Write failed to write everything";
aheinecke@45:                 return -1;
aheinecke@45:             }
aheinecke@45:         }
aheinecke@46:         if (ret != POLARSSL_ERR_NET_WANT_WRITE &&
aheinecke@46:                 ret != POLARSSL_ERR_NET_WANT_READ) {
aheinecke@45:             return ret;
aheinecke@45:         }
aheinecke@45:         tries++;
aheinecke@45:         net_usleep(100000); /* sleep 100ms to give the socket a chance
aheinecke@45:                                to clean up. */
aheinecke@45:     } while (tries < MAX_IO_TRIES);
aheinecke@45: 
aheinecke@45:     return ret;
aheinecke@45: }
aheinecke@45: 
aheinecke@46: 
aheinecke@46: int SSLConnection::reset()
aheinecke@46: {
aheinecke@46:     int ret = -1;
aheinecke@46:     ssl_close_notify(&mSSL);
aheinecke@46: 
aheinecke@46:     ret = ssl_session_reset(&mSSL);
aheinecke@46:     if (ret != 0)
aheinecke@46:     {
aheinecke@46:         qDebug() << "SSL Connection reset failed: "
aheinecke@452:                  << getPolarSSLErrorMsg(ret);
aheinecke@46:         return ret;
aheinecke@46:     }
aheinecke@46: 
aheinecke@46:     ssl_set_session(&mSSL, &mSavedSession);
aheinecke@46: 
aheinecke@46:     ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(),
aheinecke@46:                   mUrl.port(443));
aheinecke@46: 
aheinecke@46:     if (ret != 0) {
aheinecke@46:         mErrorState = NoConnection;
aheinecke@452:         qDebug() << "Connection failed." << getPolarSSLErrorMsg(ret);
aheinecke@46:         return ret;
aheinecke@46:     }
aheinecke@46: 
aheinecke@46:     while ((ret = ssl_handshake(&mSSL)) != 0) {
aheinecke@46:         if (ret != POLARSSL_ERR_NET_WANT_READ &&
aheinecke@46:                 ret != POLARSSL_ERR_NET_WANT_WRITE) {
aheinecke@46:             qDebug() << "SSL Handshake failed: "
aheinecke@452:                  << getPolarSSLErrorMsg(ret);
aheinecke@46:             mErrorState = SSLHandshakeFailed;
aheinecke@46:             return ret;
aheinecke@46:         }
aheinecke@46:     }
aheinecke@46: 
aheinecke@46:     qDebug() << "Reset connection. ";
aheinecke@46:     /* Validation should not be necessary as we reused a saved
aheinecke@46:      * session. But just to be sure. */
aheinecke@46:     return validateCertificate();
aheinecke@46: }
aheinecke@46: 
aheinecke@45: QByteArray SSLConnection::read(size_t len)
aheinecke@45: {
aheinecke@45:     unsigned char buf[len];
aheinecke@45:     QByteArray retval("");
aheinecke@45:     int ret = -1;
aheinecke@46:     unsigned int tries = 0;
aheinecke@45: 
aheinecke@46:     mNeedsReset = true;
aheinecke@45:     do {
aheinecke@45:         memset (buf, 0, sizeof(buf));
aheinecke@45:         ret = ssl_read(&mSSL, buf, len);
aheinecke@45:         if (ret == 0 ||
aheinecke@46:             ret == POLARSSL_ERR_SSL_CONN_EOF ||
aheinecke@46:             ret == POLARSSL_ERR_SSL_PEER_CLOSE_NOTIFY) {
aheinecke@45:             /* EOF */
aheinecke@45:             return retval;
aheinecke@45:         }
aheinecke@46:         if (ret == POLARSSL_ERR_NET_WANT_WRITE ||
aheinecke@46:                 ret == POLARSSL_ERR_NET_WANT_READ) {
aheinecke@46:             net_usleep(100000); /* sleep 100ms to give the socket a chance
aheinecke@46:                                    to recover */
aheinecke@46:             tries++;
aheinecke@46:         }
aheinecke@45:         if (ret <= 0) {
aheinecke@452:             qDebug() << "Read failed: " << getPolarSSLErrorMsg(ret);
aheinecke@45:             return QByteArray();
aheinecke@45:         }
aheinecke@45:         if (len < (len - (unsigned int) ret)) {
aheinecke@45:             /* Should never happen if ssl_read behaves */
aheinecke@45:             qDebug() << "integer overflow in polarSSLRead";
aheinecke@45:             return QByteArray();
aheinecke@45:         }
aheinecke@45:         len -= (unsigned int) ret;
aheinecke@46:         retval.append((const char *)buf, ret);
aheinecke@46:     } while (len > 0 && tries < MAX_IO_TRIES);
aheinecke@45: 
aheinecke@45:     return retval;
aheinecke@45: }
aheinecke@45: