/*! * \copyright Copyright (c) 2014-2017 Governikus GmbH & Co. KG, Germany */ #include "TlsChecker.h" #include "AppSettings.h" #include "Env.h" #include "SecureStorage.h" #include #include #include #include #include #include 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) { 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) { // 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; } } } qDebug() << "Check ephemeral key of type" << TlsChecker::toString(keyAlgorithm) << "and key size" << keyLength; return isValidKeyLength(keyLength, keyAlgorithm, true); } bool TlsChecker::isValidKeyLength(int pKeyLength, QSsl::KeyAlgorithm pKeyAlgorithm, bool pIsEphemeral) { const auto& secureStorage = SecureStorage::getInstance(); 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) << keySizeError; sufficient = true; } else { qWarning() << 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"); case QSsl::SslProtocol::UnknownProtocol: return QStringLiteral("UnknownProtocol"); } Q_UNREACHABLE(); } QString TlsChecker::toString(QSsl::KeyAlgorithm pKeyAlgorithm) { switch (pKeyAlgorithm) { case QSsl::KeyAlgorithm::Dsa: return QStringLiteral("Dsa"); case QSsl::KeyAlgorithm::Rsa: return QStringLiteral("Rsa"); case QSsl::KeyAlgorithm::Ec: return QStringLiteral("Ec"); default: 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) { QString msg = QStringLiteral("%1: %2").arg(static_cast(error.error())).arg(error.errorString()); if (fatalErrors.contains(error.error())) { if (AppSettings::getInstance().getGeneralSettings().isDeveloperMode()) { qCWarning(developermode) << msg; } else { qCWarning(network) << msg; fatalErrorStrings += msg; } } else { qCDebug(network) << "(ignored) " << msg; } } return fatalErrorStrings; } bool TlsChecker::containsFatalError(QNetworkReply* pReply, const QList& pErrors) { Q_ASSERT(pReply); if (getFatalErrors(pErrors).isEmpty()) { qCDebug(network) << "Ignore SSL errors"; pReply->ignoreSslErrors(); return false; } return true; } void TlsChecker::logSslConfig(const QSslConfiguration pCfg, QDebug pDebug) { QMessageLogger logger("", 0, ""); logger.info(network) << "Used session cipher" << pCfg.sessionCipher(); logger.info(network) << "Used session protocol:" << toString(pCfg.sessionProtocol()); { auto stream = logger.info(network); stream << "Used ephemeral server key:"; if (!pCfg.ephemeralServerKey().isNull()) { stream << pCfg.ephemeralServerKey(); } } logger.info(network) << "Used peer certificate:" << pCfg.peerCertificate(); pDebug << "Handshake of tls connection done!"; }