Codebase list qsslcaudit / 2c6069a src / libqsslcaudit / sslcheck.cpp
2c6069a

Tree @2c6069a (Download .tar.gz)

sslcheck.cpp @2c6069araw · history · blame

#include "sslcheck.h"
#include "clientinfo.h"


SslCheck::SslCheck()
{

}

SslCheck::~SslCheck()
{

}

const SslCheckReport SslCheckSocketErrors::doCheck(const ClientInfo *client) const
{
    SslCheckReport rep;

    // the errors here should not appear during regular communication
    // the rest is fine and are important for other checks
    if (client->socketErrors().contains(QAbstractSocket::ConnectionRefusedError)
            || client->socketErrors().contains(QAbstractSocket::HostNotFoundError)
            || client->socketErrors().contains(QAbstractSocket::SocketAccessError)
            || client->socketErrors().contains(QAbstractSocket::SocketResourceError)
            || client->socketErrors().contains(QAbstractSocket::DatagramTooLargeError)
            || client->socketErrors().contains(QAbstractSocket::NetworkError)
            || client->socketErrors().contains(QAbstractSocket::AddressInUseError)
            || client->socketErrors().contains(QAbstractSocket::SocketAddressNotAvailableError)
            || client->socketErrors().contains(QAbstractSocket::UnsupportedSocketOperationError)
            || client->socketErrors().contains(QAbstractSocket::UnfinishedSocketOperationError)
            || client->socketErrors().contains(QAbstractSocket::ProxyAuthenticationRequiredError)
            || client->socketErrors().contains(QAbstractSocket::ProxyConnectionRefusedError)
            || client->socketErrors().contains(QAbstractSocket::ProxyConnectionClosedError)
            || client->socketErrors().contains(QAbstractSocket::ProxyConnectionTimeoutError)
            || client->socketErrors().contains(QAbstractSocket::ProxyNotFoundError)
            || client->socketErrors().contains(QAbstractSocket::ProxyProtocolError)
            || client->socketErrors().contains(QAbstractSocket::OperationError)
            || client->socketErrors().contains(QAbstractSocket::TemporaryError)) {
        rep.report = QString("socket/network error occuried");
        rep.suggestedTestResult = SslTestResult::Undefined;
        rep.comment = QString("socket error");
        rep.isPassed = false;
        return rep;
    } else if (client->socketErrors().contains(QAbstractSocket::UnknownSocketError)) {
        rep.report = QString("unknown socket error occuried");
        rep.suggestedTestResult = SslTestResult::Undefined;
        rep.comment = QString("socket error");
        rep.isPassed = false;
        return rep;
    }

    rep.report = QString("");
    rep.suggestedTestResult = SslTestResult::Undefined;
    rep.comment = QString("");
    rep.isPassed = true;
    return rep;
}

const SslCheckReport SslCheckNoData::doCheck(const ClientInfo *client) const
{
    SslCheckReport rep;

    if ((client->rawDataRecv().size() == 0)
            && !client->socketErrors().contains(QAbstractSocket::RemoteHostClosedError)
            && client->socketErrors().contains(QAbstractSocket::SocketTimeoutError)) {
        rep.report = QString("no data was transmitted before timeout expired");
        rep.suggestedTestResult = SslTestResult::Undefined;
        rep.comment = QString("broken client");
        rep.isPassed = false;
        return rep;
    }

    if ((client->rawDataRecv().size() == 0)
            && client->socketErrors().contains(QAbstractSocket::RemoteHostClosedError)
            && !client->socketErrors().contains(QAbstractSocket::SocketTimeoutError)) {
        rep.report = QString("client closed the connection without transmitting any data");
        rep.suggestedTestResult = SslTestResult::Undefined;
        rep.comment = QString("broken client");
        rep.isPassed = false;
        return rep;
    }

    if (client->rawDataRecv().size() > 0) {
        rep.report = QString("");
        rep.suggestedTestResult = SslTestResult::Undefined;
        rep.comment = QString("");
        rep.isPassed = true;
        return rep;
    }

    rep.report = QString("no data received with socket in incorrect state");
    rep.suggestedTestResult = SslTestResult::UnhandledCase;
    rep.comment = QString("report this case to developers");
    rep.isPassed = false;
    return rep;
}

const SslCheckReport SslCheckNonSslData::doCheck(const ClientInfo *client) const
{
    SslCheckReport rep;

    if (client->hasHelloMessage()) {
        rep.report = QString("");
        rep.suggestedTestResult = SslTestResult::Undefined;
        rep.comment = QString("");
        rep.isPassed = true;
        return rep;
    }

    if ((client->rawDataRecv().size() > 0)
            && !client->hasHelloMessage()
            && ((client->sslErrorsStr().filter(QString("SSL23_GET_CLIENT_HELLO:http request")).size() > 0)
                || (client->sslErrorsStr().filter(QString("SSL23_GET_CLIENT_HELLO:unknown protocol")).size() > 0))) {
        rep.report = QString("%1 bytes of unexpected protocol were received before the connection was closed")
                .arg(client->rawDataRecv().size());
        rep.suggestedTestResult = SslTestResult::Undefined;
        rep.comment = QString("broken client");
        rep.isPassed = false;
        return rep;
    }

    if ((client->rawDataRecv().size() > 0)
            && !client->hasHelloMessage()
            && client->socketErrors().contains(QAbstractSocket::RemoteHostClosedError)
            && !client->socketErrors().contains(QAbstractSocket::SocketTimeoutError)) {
        rep.report = QString("%1 non-SSL bytes were received before client closed the connection")
                .arg(client->rawDataRecv().size());
        rep.suggestedTestResult = SslTestResult::Undefined;
        rep.comment = QString("broken client");
        rep.isPassed = false;
        return rep;
    }

    if ((client->rawDataRecv().size() > 0)
            && !client->hasHelloMessage()
            && !client->socketErrors().contains(QAbstractSocket::RemoteHostClosedError)
            && client->socketErrors().contains(QAbstractSocket::SocketTimeoutError)) {
        rep.report = QString("%1 non-SSL bytes were received before timeout expired")
                .arg(client->rawDataRecv().size());
        rep.suggestedTestResult = SslTestResult::Undefined;
        rep.comment = QString("broken client");
        rep.isPassed = false;
        return rep;
    }

    rep.report = QString("%1 bytes were received, however, unexpected set of other errors observed")
            .arg(client->rawDataRecv().size());
    rep.suggestedTestResult = SslTestResult::Undefined;
    rep.comment = QString("broken client");
    rep.isPassed = false;
    return rep;
}

const SslCheckReport SslCheckInvalidSsl::doCheck(const ClientInfo *client) const
{
    SslCheckReport rep;

    if (client->hasHelloMessage()
            && !client->sslConnectionEstablished()
            // specifically checking that there are no SSL-related errors
            && (((client->sslErrorsStr().size() == 1) && client->sslErrorsStr().contains("Network operation timed out"))
                || (client->sslErrorsStr().size() == 0))) {
        // client sent HELLO, but as SSL errors list is empty and encrypted connection
        // was not established, something went wrong in the middle of handshake
        // thus, consider client as non-SSL
        rep.report = QString("secure connection was not properly established (however, the attempt was made)");
        rep.suggestedTestResult = SslTestResult::Undefined;
        rep.comment = QString("broken client");
        rep.isPassed = false;
        return rep;
    }

    rep.report = QString("");
    rep.suggestedTestResult = SslTestResult::Undefined;
    rep.comment = QString("");
    rep.isPassed = true;
    return rep;
}

const SslCheckReport SslCheckForGenericSslErrors::doCheck(const ClientInfo *client) const
{
    SslCheckReport rep;

    if (client->socketErrors().contains(QAbstractSocket::SslInternalError)
            || client->socketErrors().contains(QAbstractSocket::SslInvalidUserDataError)) {
        // somehow Firefox triggers SslInternalError when refusing the proposed certificate
        // checking it here
        if (client->socketErrors().contains(QAbstractSocket::SslInternalError)) {
            int idx = client->socketErrors().indexOf(QAbstractSocket::SslInternalError);
            if ((client->sslErrorsStr().at(idx).contains("alert bad certificate"))
                    || (client->sslErrorsStr().at(idx).contains("alert unknown ca"))) {
                rep.report = QString("");
                rep.suggestedTestResult = SslTestResult::Undefined;
                rep.comment = QString("");
                rep.isPassed = true;
                return rep;
            }
        }
        rep.report = QString("failure during SSL initialization");
        rep.suggestedTestResult = SslTestResult::Undefined;
        rep.comment = QString("can't init SSL context");
        rep.isPassed = false;
        return rep;
    }

    if (!client->sslConnectionEstablished()
            && (client->socketErrors().contains(QAbstractSocket::SslHandshakeFailedError)
                && ((client->sslErrorsStr().filter(QString("SSL23_GET_CLIENT_HELLO:unknown protocol")).size() > 0)
                    || (client->sslErrorsStr().filter(QString("ssl3_get_client_hello:wrong version number")).size() > 0)))) {
        rep.report = QString("secure connection was not established, %1 bytes of unsupported TLS/SSL protocol were received before the connection was closed")
                .arg(client->rawDataRecv().size());
        rep.suggestedTestResult = SslTestResult::Undefined;
        rep.comment = QString("client proposed unsupported TLS/SSL protocol");
        rep.isPassed = false;
        return rep;
    }

    rep.report = QString("");
    rep.suggestedTestResult = SslTestResult::Undefined;
    rep.comment = QString("");
    rep.isPassed = true;
    return rep;
}

const SslCheckReport SslCheckConnectionEstablished::doCheck(const ClientInfo *client) const
{
    SslCheckReport rep;

    if (client->sslConnectionEstablished()
            && (client->interceptedData().size() > 0)) {
        rep.report = QString("test failed, client accepted fake certificate, data was intercepted");
        rep.suggestedTestResult = SslTestResult::DataIntercepted;
        rep.comment = QString("mitm possible");
        rep.isPassed = false;
        return rep;
    }

    if (client->sslConnectionEstablished()
            && (client->interceptedData().size() == 0)
            && ((client->dtlsMode() && !client->dtlsErrors().contains(XDtlsError::RemoteClosedConnectionError))
                || (!client->dtlsMode() && !client->socketErrors().contains(QAbstractSocket::RemoteHostClosedError)))) {
        rep.report = QString("test failed, client accepted fake certificate, but no data transmitted");
        rep.suggestedTestResult = SslTestResult::CertAccepted;
        rep.comment = QString("mitm possible");
        rep.isPassed = false;
        return rep;
    }

    // this is a controversion situation
    if (client->sslConnectionEstablished()
            && (client->interceptedData().size() == 0)
            && ((client->dtlsMode() && client->dtlsErrors().contains(XDtlsError::RemoteClosedConnectionError))
                || (!client->dtlsMode() && client->socketErrors().contains(QAbstractSocket::RemoteHostClosedError)))) {
        rep.report = QString("test result not clear, client established TLS session but disconnected without data transmission and explicit error message");
        rep.suggestedTestResult = SslTestResult::Undefined;
        rep.comment = QString("consider PASSED for clients which send data on the first connection. other clients test with complete MitM setup (see --forward)");
        rep.isPassed = false;
        return rep;
    }

    if (client->sslConnectionEstablished()) {
        rep.report = QString("unhandled case! please report it to developers!");
        rep.suggestedTestResult = SslTestResult::UnhandledCase;
        rep.comment = QString("report this to developers");
        rep.isPassed = false;
        return rep;
    }

    rep.report = QString("");
    rep.suggestedTestResult = SslTestResult::Undefined;
    rep.comment = QString("");
    rep.isPassed = true;
    return rep;
}

const SslCheckReport SslCheckCertificateRefused::doCheck(const ClientInfo *client) const
{
    SslCheckReport rep;

    rep.suggestedTestResult = SslTestResult::Success;
    rep.isPassed = true;

    if (!client->sslConnectionEstablished()
            && (client->dtlsMode() || (!client->dtlsMode() && client->socketErrors().contains(QAbstractSocket::SslHandshakeFailedError)))
            && ((client->sslErrorsStr().filter(QString("certificate unknown")).size() > 0)
                || (client->sslErrorsStr().filter(QString("unknown ca")).size() > 0)
                || (client->sslErrorsStr().filter(QString("bad certificate")).size() > 0))) {
        rep.report = QString("client accepted our protocol but explicitly refused our certificate");
        rep.suggestedTestResult = SslTestResult::ProtoAcceptedWithErr;
        rep.comment = QString("");
        rep.isPassed = false;
        return rep;
    }

    return rep;
}

const SslCheckReport SslCheckNoSharedCipher::doCheck(const ClientInfo *client) const
{
    SslCheckReport rep;

    rep.suggestedTestResult = SslTestResult::Success;
    rep.isPassed = true;

    if (!client->sslConnectionEstablished()
            && (client->sslErrorsStr().filter(QString("no shared cipher")).size() > 0)) {
        rep.report = QString("client accepted our protocol but explicitly refused our ciphers");
        rep.suggestedTestResult = SslTestResult::Undefined;
        rep.comment = QString("");
        rep.isPassed = false;
        return rep;
    }

    return rep;
}

const SslCheckReport SslCheckHttpsClient::doCheck(const ClientInfo *client) const
{
    SslCheckReport rep;

    if (client->hasHelloMessage()
            && (client->tlsHelloInfo.hnd_hello.alpn.contains(QByteArray("h2"))
                || client->tlsHelloInfo.hnd_hello.alpn.contains(QByteArray("http/1.1")))) {
        rep.report = QString("client identifies itself as HTTPS client");
        rep.suggestedTestResult = SslTestResult::Success;
        rep.comment = QString("");
        rep.isPassed = true;
        return rep;
    }

    if (client->hasHelloMessage()
            && client->tlsHelloInfo.hnd_hello.alpn.isEmpty()) {
        rep.report = QString("client does not explitictly define application layer protocol");
        rep.suggestedTestResult = SslTestResult::Undefined;
        rep.comment = QString("");
        rep.isPassed = false;
        return rep;
    }

    rep.report = QString("client requests some other protocol");
    rep.suggestedTestResult = SslTestResult::Undefined;
    rep.comment = QString("");
    rep.isPassed = false;
    return rep;
}