andre@908: /* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik andre@908: * Software engineering by Intevation GmbH andre@908: * andre@908: * This file is Free Software under the GNU GPL (v>=2) andre@908: * and comes with ABSOLUTELY NO WARRANTY! andre@908: * See LICENSE.txt for details. andre@908: */ andre@908: /* TODO: Wrap ssl_session in a class for reuse. andre@908: * see programs/ssl/ssl_client2.c for example of session reuse */ andre@908: #include "sslconnection_bare.h" andre@908: #include "sslhelp.h" andre@908: andre@908: #include andre@910: #include andre@908: #include andre@908: #include andre@908: andre@908: #define MAX_IO_TRIES 10 andre@908: #define MAX_RESETS 10 andre@908: andre@908: #ifdef CONNECTION_DEBUG andre@908: static void my_debug(void *ctx, int level, const char *str) andre@908: { andre@908: fprintf((FILE *) ctx, "%s", str); andre@908: fflush((FILE *) ctx); andre@908: } andre@908: #endif andre@908: andre@908: SSLConnectionBare::SSLConnectionBare(const QString& url, andre@908: const QByteArray& certificate): andre@908: SSLConnection (url, certificate) andre@908: { andre@908: int ret = -1; andre@908: andre@908: memset(&mSSL, 0, sizeof(ssl_context)); andre@908: memset(&mSavedSession, 0, sizeof( ssl_session ) ); andre@908: andre@908: ret = init(); andre@908: if (ret == 0) { andre@908: mInitialized = true; andre@908: } else { andre@908: qDebug() << "Initialization error: " + getPolarSSLErrorMsg(ret); andre@908: } andre@908: } andre@908: andre@908: int SSLConnectionBare::init() andre@908: { andre@908: int ret = -1; andre@908: QUuid uuid = QUuid::createUuid(); andre@908: QString personalString = QApplication::applicationName() + uuid.toString(); andre@908: QByteArray personalBa = personalString.toLocal8Bit(); andre@908: andre@908: x509_crt_init(&mX509PinnedCert); andre@908: entropy_init(&mEntropy); andre@908: andre@908: ret = ssl_init(&mSSL); andre@908: if (ret != 0) { andre@908: /* The only documented error is malloc failed */ andre@908: mErrorState = ErrUnknown; andre@908: return ret; andre@908: } andre@908: andre@908: /* andre@908: * Initialize random generator. andre@908: * Personalisation string, does not need to be random but andre@908: * should be unique according to documentation. andre@908: * andre@908: * the ctr_drbg structure does not need to be freed explicitly. andre@908: */ andre@908: ret = ctr_drbg_init(&mCtr_drbg, entropy_func, &mEntropy, andre@908: (const unsigned char*) personalBa.constData(), andre@908: personalBa.size()); andre@908: if (ret != 0) { andre@908: ssl_free(&mSSL); andre@908: mErrorState = ErrUnknown; andre@908: return ret; andre@908: } andre@908: andre@908: ret = x509_crt_parse(&mX509PinnedCert, andre@908: (const unsigned char*) mPinnedCert.constData(), andre@908: mPinnedCert.size()); andre@908: if (ret != 0){ andre@908: ssl_free(&mSSL); andre@908: mErrorState = InvalidPinnedCertificate; andre@908: return ret; andre@908: } andre@908: andre@908: ssl_set_endpoint(&mSSL, SSL_IS_CLIENT); andre@908: ssl_set_authmode(&mSSL, SSL_VERIFY_OPTIONAL); andre@908: ssl_set_ca_chain(&mSSL, &mX509PinnedCert, NULL, NULL); andre@908: ssl_set_renegotiation(&mSSL, SSL_RENEGOTIATION_DISABLED); andre@908: ssl_set_rng(&mSSL, ctr_drbg_random, &mCtr_drbg); andre@908: #ifdef RELEASE_BUILD andre@908: ssl_set_min_version(&mSSL, SSL_MAJOR_VERSION_3, SSL_MINOR_VERSION_3); andre@908: #endif andre@908: andre@908: #ifdef CONNECTION_DEBUG andre@908: ssl_set_dbg(&mSSL, my_debug, stdout); andre@908: #endif andre@908: andre@908: return 0; andre@908: } andre@908: andre@908: SSLConnectionBare::~SSLConnectionBare() { andre@908: disconnect(); andre@908: x509_crt_free(&mX509PinnedCert); andre@908: entropy_free(&mEntropy); andre@908: if (mInitialized) { andre@908: ssl_free(&mSSL); andre@908: } andre@908: } andre@908: andre@908: void SSLConnectionBare::disconnect() { andre@908: if (mConnected) { andre@908: ssl_close_notify(&mSSL); andre@908: if (mServerFD != -1) { andre@908: net_close(mServerFD); andre@908: mServerFD = -1; andre@908: } andre@908: ssl_session_free(&mSavedSession); andre@908: mConnected = false; andre@908: } andre@908: } andre@908: andre@908: int SSLConnectionBare::connect() { andre@908: int ret = -1; andre@908: andre@908: if (!mInitialized) { andre@908: mErrorState = ErrUnknown; andre@908: return -1; andre@908: } andre@908: andre@908: ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(), andre@908: mUrl.port(443)); andre@908: andre@908: if (ret != 0) { andre@908: qDebug() << "Connect failed: " << getPolarSSLErrorMsg(ret); andre@908: mErrorState = NoConnection; andre@908: return ret; andre@908: } andre@908: andre@908: ssl_set_bio(&mSSL, net_recv, &mServerFD, andre@908: net_send, &mServerFD); andre@908: andre@908: while ((ret = ssl_handshake(&mSSL)) != 0) { andre@908: if (ret != POLARSSL_ERR_NET_WANT_READ && andre@908: ret != POLARSSL_ERR_NET_WANT_WRITE) { andre@908: qDebug() << "SSL Handshake failed: " << getPolarSSLErrorMsg(ret); andre@908: mErrorState = SSLHandshakeFailed; andre@908: return ret; andre@908: } andre@908: } andre@908: andre@908: ret = ssl_get_session(&mSSL, &mSavedSession); andre@908: if (ret != 0) { andre@908: qDebug() << "SSL get session failed: " << getPolarSSLErrorMsg(ret); andre@908: andre@908: mErrorState = NoConnection; andre@908: return ret; andre@908: } andre@908: printf( " ok\n [ Ciphersuite is %s ]\n", andre@908: ssl_get_ciphersuite( &mSSL) ); andre@908: ret = validateCertificate(); andre@908: andre@908: if (ret == 0) { andre@908: mConnected = true; andre@908: } andre@908: return ret; andre@908: } andre@908: andre@908: int SSLConnectionBare::validateCertificate() andre@908: { andre@908: int ret = -1; andre@908: const x509_crt *peerCert = NULL; andre@908: andre@908: /* we might want to set the verify function andre@908: * with ssl_set_verify before to archive the andre@908: * certificate pinning. */ andre@908: andre@908: ret = ssl_get_verify_result(&mSSL); andre@908: andre@908: if (ret != 0 ) { andre@908: if((ret & BADCERT_EXPIRED) != 0) andre@908: qDebug() << "server certificate has expired"; andre@908: if((ret & BADCERT_REVOKED) != 0) andre@908: qDebug() << "server certificate has been revoked"; andre@908: if((ret & BADCERT_CN_MISMATCH) != 0) andre@908: qDebug() << "CN mismatch"; andre@908: if((ret & BADCERT_NOT_TRUSTED) != 0) andre@908: qDebug() << "self-signed or not signed by a trusted CA"; andre@908: #ifdef RELEASE_BUILD andre@908: mErrorState = InvalidCertificate; andre@908: return -1; andre@908: #endif andre@908: } andre@908: andre@908: peerCert = ssl_get_peer_cert(&mSSL); andre@908: andre@908: if (!peerCert) { andre@908: mErrorState = InvalidCertificate; andre@908: qDebug() << "Failed to get peer cert"; andre@908: return -1; andre@908: } andre@908: andre@908: if (peerCert->raw.len == 0 || andre@908: peerCert->raw.len != mX509PinnedCert.raw.len) { andre@908: mErrorState = InvalidCertificate; andre@908: qDebug() << "Certificate length mismatch"; andre@908: return -1; andre@908: } andre@908: andre@908: /* You can never be sure what those c++ operators do.. andre@908: if (mPinnedCert != QByteArray::fromRawData( andre@908: (const char*) peerCert->raw.p, andre@908: peerCert->raw.len)) { andre@908: qDebug() << "Certificate content mismatch"; andre@908: } andre@908: */ andre@908: andre@908: for (unsigned int i = 0; i < peerCert->raw.len; i++) { andre@908: if (peerCert->raw.p[i] != mX509PinnedCert.raw.p[i]) { andre@908: qDebug() << "Certificate content mismatch"; andre@908: mErrorState = InvalidCertificate; andre@908: return -1; andre@908: } andre@908: } andre@908: return 0; andre@908: } andre@908: andre@908: int SSLConnectionBare::write (const QByteArray& request) andre@908: { andre@908: unsigned int tries = 0; andre@908: int ret = -1; andre@908: andre@908: const unsigned char *buf = (const unsigned char *) request.constData(); andre@908: size_t len = (size_t) request.size(); andre@908: andre@908: if (mNeedsReset) { andre@908: ret = reset(); andre@908: if (ret != 0) { andre@908: qDebug() << "Reset failed: " << getPolarSSLErrorMsg(ret); andre@908: return ret; andre@908: } andre@908: } andre@908: andre@908: qDebug() << "Sending request: " << request; andre@908: /* According to doc for ssl_write: andre@908: * andre@908: * When this function returns POLARSSL_ERR_NET_WANT_WRITE, andre@908: * it must be called later with the same arguments, andre@908: * until it returns a positive value. andre@908: */ andre@908: do { andre@908: ret = ssl_write(&mSSL, buf, len); andre@908: if (ret >= 0) { andre@908: if ((unsigned int) ret == len) { andre@908: return 0; andre@908: } else { andre@908: qDebug() << "Write failed to write everything"; andre@908: return -1; andre@908: } andre@908: } andre@908: if (ret != POLARSSL_ERR_NET_WANT_WRITE && andre@908: ret != POLARSSL_ERR_NET_WANT_READ) { andre@908: return ret; andre@908: } andre@908: tries++; andre@908: net_usleep(100000); /* sleep 100ms to give the socket a chance andre@908: to clean up. */ andre@908: } while (tries < MAX_IO_TRIES); andre@908: andre@908: return ret; andre@908: } andre@908: andre@908: andre@908: int SSLConnectionBare::reset() andre@908: { andre@908: int ret = -1; andre@908: ssl_close_notify(&mSSL); andre@908: andre@908: ret = ssl_session_reset(&mSSL); andre@908: if (ret != 0) andre@908: { andre@908: qDebug() << "SSL Connection reset failed: " andre@908: << getPolarSSLErrorMsg(ret); andre@908: return ret; andre@908: } andre@908: andre@908: ssl_set_session(&mSSL, &mSavedSession); andre@908: andre@908: ret = net_connect(&mServerFD, mUrl.host().toLatin1().constData(), andre@908: mUrl.port(443)); andre@908: andre@908: if (ret != 0) { andre@908: mErrorState = NoConnection; andre@908: qDebug() << "Connection failed." << getPolarSSLErrorMsg(ret); andre@908: return ret; andre@908: } andre@908: andre@908: while ((ret = ssl_handshake(&mSSL)) != 0) { andre@908: if (ret != POLARSSL_ERR_NET_WANT_READ && andre@908: ret != POLARSSL_ERR_NET_WANT_WRITE) { andre@908: qDebug() << "SSL Handshake failed: " andre@908: << getPolarSSLErrorMsg(ret); andre@908: mErrorState = SSLHandshakeFailed; andre@908: return ret; andre@908: } andre@908: } andre@908: andre@908: qDebug() << "Reset connection. "; andre@908: /* Validation should not be necessary as we reused a saved andre@908: * session. But just to be sure. */ andre@908: return validateCertificate(); andre@908: } andre@908: andre@908: QByteArray SSLConnectionBare::read(size_t len) andre@908: { andre@908: unsigned char buf[len]; andre@908: QByteArray retval(""); andre@908: int ret = -1; andre@908: unsigned int tries = 0; andre@908: andre@908: mNeedsReset = true; andre@908: do { andre@908: memset (buf, 0, sizeof(buf)); andre@908: ret = ssl_read(&mSSL, buf, len); andre@908: if (ret == 0 || andre@908: ret == POLARSSL_ERR_SSL_CONN_EOF || andre@908: ret == POLARSSL_ERR_SSL_PEER_CLOSE_NOTIFY) { andre@908: /* EOF */ andre@908: return retval; andre@908: } andre@908: if (ret == POLARSSL_ERR_NET_WANT_WRITE || andre@908: ret == POLARSSL_ERR_NET_WANT_READ) { andre@908: net_usleep(100000); /* sleep 100ms to give the socket a chance andre@908: to recover */ andre@908: tries++; andre@908: } andre@908: if (ret <= 0) { andre@908: qDebug() << "Read failed: " << getPolarSSLErrorMsg(ret); andre@908: return QByteArray(); andre@908: } andre@908: if (len < (len - (unsigned int) ret)) { andre@908: /* Should never happen if ssl_read behaves */ andre@908: qDebug() << "integer overflow in polarSSLRead"; andre@908: return QByteArray(); andre@908: } andre@908: len -= (unsigned int) ret; andre@908: retval.append((const char *)buf, ret); andre@908: } while (len > 0 && tries < MAX_IO_TRIES); andre@908: andre@908: return retval; andre@908: } andre@908: andre@910: QMap SSLConnectionBare::parseHeaders(QByteArray *data) andre@910: { andre@910: int bodyStart = data->indexOf("\r\n\r\n"); andre@910: QMap retval; andre@910: QByteArray headers; andre@910: QString response(*data); andre@910: if (bodyStart == -1) { andre@910: qDebug() << "Could not find header end."; andre@910: return retval; andre@910: } andre@908: andre@910: /* Take the headers with one additional line break */ andre@910: headers = data->left(bodyStart + 2); andre@910: /* Chop off the head */ andre@910: andre@910: foreach (const QString& line, response.split("\r\n")) { andre@910: int sepPos = -1; andre@910: sepPos = line.indexOf(": "); andre@910: if (sepPos == -1) { andre@910: continue; andre@910: } andre@910: QString key = line.left(sepPos); andre@910: QString value = line.right(line.size() - sepPos - 2); andre@910: andre@910: retval.insert(key, value); andre@910: } andre@910: andre@910: *data = data->right(data->size() - bodyStart - 4); andre@910: return retval; andre@910: } andre@910: andre@910: QDateTime SSLConnectionBare::getLastModifiedHeader(const QString &resource) { andre@910: int ret = -1; andre@910: QByteArray response; andre@910: QLocale cLocale = QLocale::c(); andre@910: QMap headers; andre@910: QString headRequest = andre@910: QString::fromLatin1("HEAD %1 HTTP/1.0\r\n\r\n").arg(resource); andre@910: andre@910: ret = write(headRequest.toUtf8()); andre@910: if (ret != 0) { andre@910: return QDateTime(); andre@910: } andre@910: andre@910: response = read(1024); andre@910: andre@910: qDebug() << "Response from server was: " << response; andre@910: andre@910: if (response.isNull()) { andre@910: qDebug() << "No response"; andre@910: return QDateTime(); andre@910: } andre@910: andre@910: headers = parseHeaders(&response); andre@910: const QString lastModified = headers.value("Last-Modified"); andre@910: if (!lastModified.isEmpty()) { andre@910: QDateTime candidate = cLocale.toDateTime(lastModified, andre@910: "ddd, dd MMM yyyy HH:mm:ss' GMT'"); andre@910: if (candidate.isValid()) { andre@910: return candidate; andre@910: } andre@910: } andre@910: return QDateTime(); andre@910: } andre@910: andre@910: bool SSLConnectionBare::downloadFile(const QString &resource, andre@910: const QString &fileName, andre@910: size_t maxSize) andre@910: { andre@910: int ret = -1; andre@910: size_t bytesRead = 0; andre@910: QString getRequest = andre@910: QString::fromLatin1("GET %1 HTTP/1.0\r\n\r\n").arg(resource); andre@910: andre@910: QSaveFile outputFile(fileName); andre@910: andre@910: ret = write(getRequest.toUtf8()); andre@910: andre@913: if (ret != 0) { andre@913: qDebug() << "Failed to send request."; andre@913: return false; andre@913: } andre@913: andre@910: // Open / Create the file to write to. andre@910: if (!outputFile.open(QIODevice::WriteOnly)) { andre@910: qDebug() << "Failed to open file"; andre@910: return false; andre@910: } andre@910: andre@910: bool inBody = false; andre@910: QMap headers; andre@910: do { andre@910: /* Read the response in 8KiB chunks */ andre@910: int responseSize = 0; andre@910: QByteArray response = read(8192); andre@910: if (response.isNull()) { andre@910: qDebug() << "Error reading response"; andre@910: return false; andre@910: } andre@910: responseSize = response.size(); andre@910: if (!inBody) { andre@910: headers = parseHeaders(&response); andre@910: inBody = true; andre@910: } andre@910: outputFile.write(response); andre@910: bytesRead += responseSize; andre@910: if (responseSize < 8192) { andre@910: /* Nothing more to read */ andre@910: break; andre@910: } andre@910: } while (bytesRead < maxSize); andre@910: andre@910: return outputFile.commit(); andre@910: } andre@910: andre@990: void SSLConnectionBare::setCiphersuites(int ciphers[]) { andre@990: if (mInitialized) { andre@990: ssl_set_ciphersuites(&mSSL, ciphers); andre@990: } andre@990: }