/*! * \copyright Copyright (c) 2014-2019 Governikus GmbH & Co. KG, Germany */ #include "TlsChecker.h" #include "AppSettings.h" #include "SecureStorage.h" #include #include #if (QT_VERSION < QT_VERSION_CHECK(5, 13, 0)) #include #include #include #include #include #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& 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(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(); 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()->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& pErrors) { static const QSet 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()->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& 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!"; }