319 lines
8.2 KiB
C++
319 lines
8.2 KiB
C++
/*!
|
|
* \copyright Copyright (c) 2014-2019 Governikus GmbH & Co. KG, Germany
|
|
*/
|
|
|
|
#include "TlsChecker.h"
|
|
|
|
#include "AppSettings.h"
|
|
#include "SecureStorage.h"
|
|
|
|
#include <QCryptographicHash>
|
|
#include <QLoggingCategory>
|
|
|
|
#if (QT_VERSION < QT_VERSION_CHECK(5, 13, 0))
|
|
#include <openssl/dh.h>
|
|
#include <openssl/dsa.h>
|
|
#include <openssl/ec.h>
|
|
#include <openssl/evp.h>
|
|
#include <openssl/rsa.h>
|
|
#endif
|
|
|
|
Q_DECLARE_LOGGING_CATEGORY(developermode)
|
|
Q_DECLARE_LOGGING_CATEGORY(network)
|
|
|
|
using namespace governikus;
|
|
|
|
|
|
bool TlsChecker::checkCertificate(const QSslCertificate& pCertificate,
|
|
QCryptographicHash::Algorithm pAlgorithm,
|
|
const QSet<QString>& pAcceptedCertificateHashes)
|
|
{
|
|
qDebug() << "Check certificate CN=" << pCertificate.subjectInfo(QSslCertificate::CommonName) << "SN=" << pCertificate.serialNumber();
|
|
QString hash = QString::fromLatin1(pCertificate.digest(pAlgorithm).toHex());
|
|
qDebug() << "Certificate hash(" << pAlgorithm << ")" << hash;
|
|
qDebug() << "Accepted certificate hashes" << pAcceptedCertificateHashes;
|
|
for (const auto& acceptedHash : pAcceptedCertificateHashes)
|
|
{
|
|
if (hash.compare(acceptedHash, Qt::CaseInsensitive) == 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
bool TlsChecker::hasValidCertificateKeyLength(const QSslCertificate& pCertificate)
|
|
{
|
|
if (pCertificate.isNull())
|
|
{
|
|
qDebug() << "Certificate is null";
|
|
return false;
|
|
}
|
|
|
|
auto keyLength = pCertificate.publicKey().length();
|
|
auto keyAlgorithm = pCertificate.publicKey().algorithm();
|
|
qDebug() << "Check certificate key of type" << TlsChecker::toString(keyAlgorithm) << "and key size" << keyLength;
|
|
return isValidKeyLength(keyLength, keyAlgorithm, false);
|
|
}
|
|
|
|
|
|
bool TlsChecker::hasValidEphemeralKeyLength(const QSslKey& pEphemeralServerKey)
|
|
{
|
|
int keyLength = pEphemeralServerKey.length();
|
|
QSsl::KeyAlgorithm keyAlgorithm = pEphemeralServerKey.algorithm();
|
|
|
|
if (keyAlgorithm == QSsl::Opaque)
|
|
{
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
|
|
qWarning() << "Qt failed to determine algorithm";
|
|
#else
|
|
qWarning() << "Qt failed to determine algorithm... fallback to internal handling";
|
|
// try do determine key algorithm and length
|
|
if (auto key = static_cast<EVP_PKEY*>(pEphemeralServerKey.handle()))
|
|
{
|
|
keyLength = EVP_PKEY_bits(key);
|
|
switch (EVP_PKEY_base_id(key))
|
|
{
|
|
case EVP_PKEY_RSA:
|
|
keyAlgorithm = QSsl::Rsa;
|
|
break;
|
|
|
|
case EVP_PKEY_DSA:
|
|
/* fall through */
|
|
case EVP_PKEY_DH:
|
|
keyAlgorithm = QSsl::Dsa;
|
|
break;
|
|
|
|
case EVP_PKEY_EC:
|
|
keyAlgorithm = QSsl::Ec;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
qDebug() << "Check ephemeral key of type" << TlsChecker::toString(keyAlgorithm) << "and key size" << keyLength;
|
|
return isValidKeyLength(keyLength, keyAlgorithm, true);
|
|
}
|
|
|
|
|
|
QString TlsChecker::getCertificateIssuerName(const QSslCertificate& pCertificate)
|
|
{
|
|
const auto& issuerNameList = pCertificate.issuerInfo(QSslCertificate::CommonName);
|
|
if (!issuerNameList.isEmpty())
|
|
{
|
|
return issuerNameList.first();
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
|
|
bool TlsChecker::isValidKeyLength(int pKeyLength, QSsl::KeyAlgorithm pKeyAlgorithm, bool pIsEphemeral)
|
|
{
|
|
const auto* secureStorage = Env::getSingleton<SecureStorage>();
|
|
const int minKeySize = pIsEphemeral ? secureStorage->getMinimumEphemeralKeySize(pKeyAlgorithm) : secureStorage->getMinimumStaticKeySize(pKeyAlgorithm);
|
|
|
|
qDebug() << "Minimum requested key size" << minKeySize;
|
|
|
|
bool sufficient = pKeyLength >= minKeySize;
|
|
if (!sufficient)
|
|
{
|
|
auto keySizeError = QStringLiteral("%1 key with insufficient key size found %2").arg(toString(pKeyAlgorithm)).arg(pKeyLength);
|
|
if (Env::getSingleton<AppSettings>()->getGeneralSettings().isDeveloperMode())
|
|
{
|
|
qCWarning(developermode).noquote() << keySizeError;
|
|
sufficient = true;
|
|
}
|
|
else
|
|
{
|
|
qWarning().noquote() << keySizeError;
|
|
}
|
|
}
|
|
return sufficient;
|
|
}
|
|
|
|
|
|
QString TlsChecker::toString(QSsl::SslProtocol pProtocol)
|
|
{
|
|
switch (pProtocol)
|
|
{
|
|
case QSsl::SslProtocol::AnyProtocol:
|
|
return QStringLiteral("AnyProtocol");
|
|
|
|
case QSsl::SslProtocol::SecureProtocols:
|
|
return QStringLiteral("SecureProtocols");
|
|
|
|
case QSsl::SslProtocol::SslV2:
|
|
return QStringLiteral("SslV2");
|
|
|
|
case QSsl::SslProtocol::SslV3:
|
|
return QStringLiteral("SslV3");
|
|
|
|
case QSsl::SslProtocol::TlsV1SslV3:
|
|
return QStringLiteral("TlsV1SslV3");
|
|
|
|
case QSsl::SslProtocol::TlsV1_0:
|
|
return QStringLiteral("TlsV1_0");
|
|
|
|
case QSsl::SslProtocol::TlsV1_0OrLater:
|
|
return QStringLiteral("TlsV1_0OrLater");
|
|
|
|
case QSsl::SslProtocol::TlsV1_1:
|
|
return QStringLiteral("TlsV1_1");
|
|
|
|
case QSsl::SslProtocol::TlsV1_1OrLater:
|
|
return QStringLiteral("TlsV1_1OrLater");
|
|
|
|
case QSsl::SslProtocol::TlsV1_2:
|
|
return QStringLiteral("TlsV1_2");
|
|
|
|
case QSsl::SslProtocol::TlsV1_2OrLater:
|
|
return QStringLiteral("TlsV1_2OrLater");
|
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
|
case QSsl::SslProtocol::TlsV1_3:
|
|
return QStringLiteral("TlsV1_3");
|
|
|
|
case QSsl::SslProtocol::TlsV1_3OrLater:
|
|
return QStringLiteral("TlsV1_3OrLater");
|
|
|
|
case QSsl::SslProtocol::DtlsV1_0:
|
|
return QStringLiteral("DtlsV1_0");
|
|
|
|
case QSsl::SslProtocol::DtlsV1_0OrLater:
|
|
return QStringLiteral("DtlsV1_0OrLater");
|
|
|
|
case QSsl::SslProtocol::DtlsV1_2:
|
|
return QStringLiteral("DtlsV1_2");
|
|
|
|
case QSsl::SslProtocol::DtlsV1_2OrLater:
|
|
return QStringLiteral("DtlsV1_2OrLater");
|
|
|
|
#endif
|
|
case QSsl::SslProtocol::UnknownProtocol:
|
|
return QStringLiteral("UnknownProtocol");
|
|
}
|
|
|
|
Q_UNREACHABLE();
|
|
return QString();
|
|
}
|
|
|
|
|
|
QString TlsChecker::toString(QSsl::KeyAlgorithm pKeyAlgorithm)
|
|
{
|
|
switch (pKeyAlgorithm)
|
|
{
|
|
case QSsl::KeyAlgorithm::Opaque:
|
|
return QStringLiteral("Opaque");
|
|
|
|
case QSsl::KeyAlgorithm::Dsa:
|
|
return QStringLiteral("Dsa");
|
|
|
|
case QSsl::KeyAlgorithm::Rsa:
|
|
return QStringLiteral("Rsa");
|
|
|
|
case QSsl::KeyAlgorithm::Ec:
|
|
return QStringLiteral("Ec");
|
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
|
|
case QSsl::KeyAlgorithm::Dh:
|
|
return QStringLiteral("Dh");
|
|
|
|
#endif
|
|
}
|
|
|
|
return QStringLiteral("Unknown (%1)").arg(pKeyAlgorithm);
|
|
}
|
|
|
|
|
|
QStringList TlsChecker::getFatalErrors(const QList<QSslError>& pErrors)
|
|
{
|
|
static const QSet<QSslError::SslError> fatalErrors(
|
|
{
|
|
QSslError::CertificateSignatureFailed, QSslError::CertificateNotYetValid, QSslError::CertificateExpired,
|
|
QSslError::InvalidNotBeforeField, QSslError::InvalidNotAfterField, QSslError::CertificateRevoked, QSslError::InvalidCaCertificate,
|
|
QSslError::CertificateRejected, QSslError::SubjectIssuerMismatch, QSslError::HostNameMismatch,
|
|
QSslError::UnspecifiedError, QSslError::NoSslSupport, QSslError::CertificateBlacklisted
|
|
});
|
|
|
|
QStringList fatalErrorStrings;
|
|
for (const auto& error : pErrors)
|
|
{
|
|
const auto& msg = error.errorString();
|
|
if (fatalErrors.contains(error.error()))
|
|
{
|
|
if (Env::getSingleton<AppSettings>()->getGeneralSettings().isDeveloperMode())
|
|
{
|
|
qCWarning(developermode) << msg;
|
|
if (!error.certificate().isNull())
|
|
{
|
|
qCWarning(developermode) << error.certificate();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qCWarning(network) << msg;
|
|
if (!error.certificate().isNull())
|
|
{
|
|
qCWarning(network) << error.certificate();
|
|
}
|
|
fatalErrorStrings += msg;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qCDebug(network) << "(ignored)" << msg;
|
|
}
|
|
}
|
|
return fatalErrorStrings;
|
|
}
|
|
|
|
|
|
bool TlsChecker::containsFatalError(QNetworkReply* pReply, const QList<QSslError>& pErrors)
|
|
{
|
|
if (pReply == nullptr)
|
|
{
|
|
qCCritical(network) << "Reply should not be nullptr";
|
|
Q_ASSERT(pReply);
|
|
return false;
|
|
}
|
|
|
|
if (getFatalErrors(pErrors).isEmpty())
|
|
{
|
|
qCDebug(network) << "Ignore SSL errors";
|
|
pReply->ignoreSslErrors();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void TlsChecker::logSslConfig(const QSslConfiguration& pCfg, const QMessageLogger& pLogger)
|
|
{
|
|
pLogger.info() << "Used session cipher" << pCfg.sessionCipher();
|
|
pLogger.info() << "Used session protocol:" << toString(pCfg.sessionProtocol());
|
|
|
|
{
|
|
auto stream = pLogger.info();
|
|
stream << "Used ephemeral server key:";
|
|
if (!pCfg.ephemeralServerKey().isNull())
|
|
{
|
|
stream << pCfg.ephemeralServerKey();
|
|
}
|
|
}
|
|
|
|
pLogger.info() << "Used peer certificate:" << pCfg.peerCertificate();
|
|
|
|
auto session = pCfg.sessionTicket();
|
|
if (!session.isEmpty())
|
|
{
|
|
// do not print session ticket as plain text
|
|
session = QCryptographicHash::hash(session, QCryptographicHash::Sha256).toHex();
|
|
}
|
|
pLogger.info() << "Used ssl session:" << session;
|
|
pLogger.info() << "Handshake of tls connection done!";
|
|
}
|