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@45: aheinecke@45: #include aheinecke@45: #include aheinecke@45: #include 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: QString getErrorMsg(int ret) aheinecke@45: { aheinecke@45: char errbuf[255]; aheinecke@45: polarssl_strerror(ret, errbuf, 255); aheinecke@45: errbuf[254] = '\0'; /* Just to be sure */ aheinecke@45: return QString::fromLatin1(errbuf); aheinecke@45: } 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), 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@45: QFile certResource(":certs/kolab.org"); 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@45: qDebug() << "Initialization error: " + getErrorMsg(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@46: qDebug() << "Connect failed: " << getErrorMsg(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@46: qDebug() << "SSL Handshake failed: " << getErrorMsg(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@46: qDebug() << "SSL get session failed: " << getErrorMsg(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@46: qDebug() << "Reset failed: " << getErrorMsg(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@46: << getErrorMsg(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@46: qDebug() << "Connection failed." << getErrorMsg(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@46: << getErrorMsg(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@45: qDebug() << "Read failed: " << getErrorMsg(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: