/**************************************************************************** * Copyright (C) 2013-2016 by Savoir-faire Linux * * Author : Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see <http://www.gnu.org/licenses/>. * ***************************************************************************/ #include "securityevaluationmodel.h" //Qt #include <QtCore/QIdentityProxyModel> #include <QtCore/QTimer> //Ring #include "account.h" #include "certificatemodel.h" #include "globalinstances.h" #include "interfaces/pixmapmanipulatori.h" #include "private/securityevaluationmodel_p.h" #include "securityflaw.h" #include "private/securityflaw_p.h" #include "private/certificate_p.h" #include <QtAlgorithms> const QString SecurityEvaluationModelPrivate::messages[enum_class_size<SecurityEvaluationModel::AccountSecurityChecks>()] = { /*SRTP_ENABLED */QObject::tr("Your media streams are not encrypted, please enable SDES"), /*TLS_ENABLED */QObject::tr("TLS is disabled, the negotiation won't be encrypted. Your communication will be vulnerable to " "snooping"), /*CERTIFICATE_MATCH */QObject::tr("Your certificate and authority don't match, if your certificate require an authority, it won't work"), /*OUTGOING_SERVER_MATCH */QObject::tr("The outgoing server specified doesn't match the hostname or the one included in the certificate"), /*VERIFY_INCOMING_ENABLED */QObject::tr("The \"verify incoming certificate\" option is disabled, this leave you vulnerable to man in the middle attack"), /*VERIFY_ANSWER_ENABLED */QObject::tr("The \"verify answer certificate\" option is disabled, this leave you vulnerable to man in the middle attack"), /*REQUIRE_CERTIFICATE_ENABLED */QObject::tr("None of your certificate provide a private key, this is required. Please select a private key" " or use a certificate with one built-in"), /* NOT_MISSING_CERTIFICATE */QObject::tr("No certificate has been provided. This is, for now, unsupported by Ring"), /* NOT_MISSING_AUTHORITY */QObject::tr("No certificate authority is provided, it won't be possible to validate if the answer certificates are valid. Some account may also not work."), }; static const QString s1 = QObject::tr("Your certificate is expired, please contact your system administrator."); static const QString s2 = QObject::tr("Your certificate is self signed. This break the chain of trust."); const TypedStateMachine< SecurityEvaluationModel::SecurityLevel , SecurityEvaluationModel::AccountSecurityChecks > SecurityEvaluationModelPrivate::maximumSecurityLevel = {{ /* SRTP_ENABLED */ SecurityEvaluationModel::SecurityLevel::NONE , /* TLS_ENABLED */ SecurityEvaluationModel::SecurityLevel::NONE , /* CERTIFICATE_MATCH */ SecurityEvaluationModel::SecurityLevel::WEAK , /* OUTGOING_SERVER_MATCH */ SecurityEvaluationModel::SecurityLevel::MEDIUM , /* VERIFY_INCOMING_ENABLED */ SecurityEvaluationModel::SecurityLevel::MEDIUM , /* VERIFY_ANSWER_ENABLED */ SecurityEvaluationModel::SecurityLevel::MEDIUM , /* REQUIRE_CERTIFICATE_ENABLED */ SecurityEvaluationModel::SecurityLevel::WEAK , /* NOT_MISSING_CERTIFICATE */ SecurityEvaluationModel::SecurityLevel::WEAK , /* NOT_MISSING_AUTHORITY */ SecurityEvaluationModel::SecurityLevel::ACCEPTABLE , }}; const TypedStateMachine< SecurityEvaluationModel::Severity , SecurityEvaluationModel::AccountSecurityChecks > SecurityEvaluationModelPrivate::flawSeverity = {{ /* SRTP_ENABLED */ SecurityEvaluationModel::Severity::ISSUE , /* TLS_ENABLED */ SecurityEvaluationModel::Severity::ISSUE , /* CERTIFICATE_MATCH */ SecurityEvaluationModel::Severity::ERROR , /* OUTGOING_SERVER_MATCH */ SecurityEvaluationModel::Severity::WARNING , /* VERIFY_INCOMING_ENABLED */ SecurityEvaluationModel::Severity::ISSUE , /* VERIFY_ANSWER_ENABLED */ SecurityEvaluationModel::Severity::ISSUE , /* REQUIRE_CERTIFICATE_ENABLED */ SecurityEvaluationModel::Severity::ISSUE , /* NOT_MISSING_CERTIFICATE */ SecurityEvaluationModel::Severity::WARNING , /* NOT_MISSING_AUTHORITY */ SecurityEvaluationModel::Severity::INFORMATION , }}; const TypedStateMachine< SecurityEvaluationModel::SecurityLevel , Certificate::Checks > SecurityEvaluationModelPrivate::maximumCertificateSecurityLevel = {{ /* HAS_PRIVATE_KEY */ SecurityEvaluationModel::SecurityLevel::NONE , /* EXPIRED */ SecurityEvaluationModel::SecurityLevel::MEDIUM , /* STRONG_SIGNING */ SecurityEvaluationModel::SecurityLevel::WEAK , /* NOT_SELF_SIGNED */ SecurityEvaluationModel::SecurityLevel::MEDIUM , /* KEY_MATCH */ SecurityEvaluationModel::SecurityLevel::NONE , /* PRIVATE_KEY_STORAGE_PERMISSION */ SecurityEvaluationModel::SecurityLevel::MEDIUM , /* PUBLIC_KEY_STORAGE_PERMISSION */ SecurityEvaluationModel::SecurityLevel::MEDIUM , /* PRIVATE_KEY_DIRECTORY_PERMISSIONS */ SecurityEvaluationModel::SecurityLevel::MEDIUM , /* PUBLIC_KEY_DIRECTORY_PERMISSIONS */ SecurityEvaluationModel::SecurityLevel::MEDIUM , /* PRIVATE_KEY_STORAGE_LOCATION */ SecurityEvaluationModel::SecurityLevel::ACCEPTABLE , /* PUBLIC_KEY_STORAGE_LOCATION */ SecurityEvaluationModel::SecurityLevel::ACCEPTABLE , /* PRIVATE_KEY_SELINUX_ATTRIBUTES */ SecurityEvaluationModel::SecurityLevel::ACCEPTABLE , /* PUBLIC_KEY_SELINUX_ATTRIBUTES */ SecurityEvaluationModel::SecurityLevel::ACCEPTABLE , /* EXIST */ SecurityEvaluationModel::SecurityLevel::NONE , /* VALID */ SecurityEvaluationModel::SecurityLevel::NONE , /* VALID_AUTHORITY */ SecurityEvaluationModel::SecurityLevel::MEDIUM , /* KNOWN_AUTHORITY */ SecurityEvaluationModel::SecurityLevel::ACCEPTABLE , //TODO figure out of the impact of this /* NOT_REVOKED */ SecurityEvaluationModel::SecurityLevel::WEAK , /* AUTHORITY_MATCH */ SecurityEvaluationModel::SecurityLevel::NONE , /* EXPECTED_OWNER */ SecurityEvaluationModel::SecurityLevel::MEDIUM , //TODO figure out of the impact of this /* ACTIVATED */ SecurityEvaluationModel::SecurityLevel::MEDIUM , //TODO figure out of the impact of this }}; static const Matrix1D<Certificate::Checks, bool> relevantWithoutPrivateKey = { { Certificate::Checks::HAS_PRIVATE_KEY , false }, { Certificate::Checks::EXPIRED , true }, { Certificate::Checks::STRONG_SIGNING , true }, { Certificate::Checks::NOT_SELF_SIGNED , true }, { Certificate::Checks::KEY_MATCH , false }, { Certificate::Checks::PRIVATE_KEY_STORAGE_PERMISSION , true }, { Certificate::Checks::PUBLIC_KEY_STORAGE_PERMISSION , true }, { Certificate::Checks::PRIVATE_KEY_DIRECTORY_PERMISSIONS, true }, { Certificate::Checks::PUBLIC_KEY_DIRECTORY_PERMISSIONS , true }, { Certificate::Checks::PRIVATE_KEY_STORAGE_LOCATION , true }, { Certificate::Checks::PUBLIC_KEY_STORAGE_LOCATION , true }, { Certificate::Checks::PRIVATE_KEY_SELINUX_ATTRIBUTES , true }, { Certificate::Checks::PUBLIC_KEY_SELINUX_ATTRIBUTES , true }, { Certificate::Checks::EXIST , true }, { Certificate::Checks::VALID , true }, { Certificate::Checks::VALID_AUTHORITY , true }, { Certificate::Checks::KNOWN_AUTHORITY , true }, { Certificate::Checks::NOT_REVOKED , true }, { Certificate::Checks::AUTHORITY_MATCH , true }, { Certificate::Checks::EXPECTED_OWNER , true }, { Certificate::Checks::ACTIVATED , true }, }; const TypedStateMachine< SecurityEvaluationModel::Severity , Certificate::Checks > SecurityEvaluationModelPrivate::certificateFlawSeverity = {{ /* HAS_PRIVATE_KEY */ SecurityEvaluationModel::Severity::ERROR , /* EXPIRED */ SecurityEvaluationModel::Severity::WARNING , /* STRONG_SIGNING */ SecurityEvaluationModel::Severity::ISSUE , /* NOT_SELF_SIGNED */ SecurityEvaluationModel::Severity::WARNING , /* KEY_MATCH */ SecurityEvaluationModel::Severity::ERROR , /* PRIVATE_KEY_STORAGE_PERMISSION */ SecurityEvaluationModel::Severity::WARNING , /* PUBLIC_KEY_STORAGE_PERMISSION */ SecurityEvaluationModel::Severity::WARNING , /* PRIVATE_KEY_DIRECTORY_PERMISSIONS */ SecurityEvaluationModel::Severity::WARNING , /* PUBLIC_KEY_DIRECTORY_PERMISSIONS */ SecurityEvaluationModel::Severity::WARNING , /* PRIVATE_KEY_STORAGE_LOCATION */ SecurityEvaluationModel::Severity::INFORMATION , /* PUBLIC_KEY_STORAGE_LOCATION */ SecurityEvaluationModel::Severity::INFORMATION , /* PRIVATE_KEY_SELINUX_ATTRIBUTES */ SecurityEvaluationModel::Severity::INFORMATION , /* PUBLIC_KEY_SELINUX_ATTRIBUTES */ SecurityEvaluationModel::Severity::INFORMATION , /* EXIST */ SecurityEvaluationModel::Severity::ERROR , /* VALID */ SecurityEvaluationModel::Severity::ERROR , /* VALID_AUTHORITY */ SecurityEvaluationModel::Severity::WARNING , /* KNOWN_AUTHORITY */ SecurityEvaluationModel::Severity::WARNING , /* NOT_REVOKED */ SecurityEvaluationModel::Severity::ISSUE , /* AUTHORITY_MATCH */ SecurityEvaluationModel::Severity::ISSUE , /* EXPECTED_OWNER */ SecurityEvaluationModel::Severity::WARNING , /* ACTIVATED */ SecurityEvaluationModel::Severity::WARNING , }}; /** * This class add a prefix in front of Qt::DisplayRole to add a disambiguation * when there is multiple certificates in the same SecurityEvaluationModel and * also add new roles such as the severity, BackgroundRole and DecorationRole */ class PrefixAndSeverityProxyModel : public QIdentityProxyModel { Q_OBJECT public: explicit PrefixAndSeverityProxyModel(const QString& prefix,QAbstractItemModel* parent); virtual QModelIndex index ( int row , int column, const QModelIndex& parent ) const override; virtual QVariant data ( const QModelIndex& index , int role ) const override; virtual int columnCount ( const QModelIndex& parent ) const override; //Attributes QString m_Name; }; /** * This model transform accounts attributes into security checks to validate if * some options reduce the security level. */ class AccountChecksModel : public QAbstractTableModel { Q_OBJECT public: enum class Columns { MESSAGE , SOURCE , RESULT , COUNT__ }; AccountChecksModel(const Account* a); //Model functions virtual QVariant data ( const QModelIndex& index, int role = Qt::DisplayRole ) const override; virtual int rowCount ( const QModelIndex& parent = QModelIndex() ) const override; virtual int columnCount ( const QModelIndex& parent = QModelIndex() ) const override; virtual Qt::ItemFlags flags ( const QModelIndex& index ) const override; virtual bool setData ( const QModelIndex& index, const QVariant &value, int role) override; virtual QHash<int,QByteArray> roleNames() const override; //Helpers void update(); private: //Attributes const Account* m_pAccount; Matrix1D<SecurityEvaluationModel::AccountSecurityChecks, Certificate::CheckValues> m_lCachedResults; }; /** * This model take multiple listModels and append them one after the other * * the trick for high performance is to known at compile time the sizes */ class CombinaisonProxyModel : public QAbstractTableModel { Q_OBJECT public: explicit CombinaisonProxyModel( QAbstractItemModel* publicCert , QAbstractItemModel* caCert , QAbstractItemModel* account , QObject* parent ); //Model functions virtual QVariant data ( const QModelIndex& index, int role = Qt::DisplayRole ) const override; virtual int rowCount ( const QModelIndex& parent = QModelIndex() ) const override; virtual int columnCount ( const QModelIndex& parent = QModelIndex() ) const override; virtual Qt::ItemFlags flags ( const QModelIndex& index ) const override; virtual bool setData ( const QModelIndex& index, const QVariant &value, int role) override; virtual QHash<int,QByteArray> roleNames() const override; private: //Attributes QVector<QAbstractItemModel*> m_lSources; ///All source model, in order enum Src { CA = 0, /*!< Rows allocated for the certificate authority */ PK = 1, /*!< Rows allocated for the public certificate */ AC = 2, /*!< Rows allocated for the account settions */ ER = 3, /*!< TODO Rows allocated for runtime error */ }; ///This model expect a certain size, get each sections size constexpr static const short sizes[] = { enum_class_size< Certificate :: Checks > (), enum_class_size< Certificate :: Checks > (), enum_class_size< SecurityEvaluationModel :: AccountSecurityChecks > (), }; ///Get the combined size constexpr inline static int totalSize() { return sizes[CA] + sizes[PK] + sizes[AC]+1; } ///Get a model index from a value constexpr inline static int toModelIdx(const int value) { return (value >= sizes[CA] + sizes[PK] ? AC : ( value >= sizes[PK] ? PK : CA ) ); } //Compute the correct index.row() offset constexpr inline static int fromFinal(const int value) { return (value >= sizes[CA] + sizes[PK] ? value - sizes[CA] - sizes[PK] : ( value >= sizes[PK] ? value - sizes[CA] : value ) ); } }; constexpr const short CombinaisonProxyModel::sizes[]; ///Create a callback map for signals to avoid a large switch(){} in the code static const Matrix1D<SecurityEvaluationModel::Severity, void(SecurityEvaluationModel::*)()> m_lSignalMap = {{ /* UNSUPPORTED */ nullptr , /* INFORMATION */ &SecurityEvaluationModel::informationCountChanged , /* WARN1NG */ &SecurityEvaluationModel::warningCountChanged , /* ISSUE */ &SecurityEvaluationModel::issueCountChanged , /* ERROR */ &SecurityEvaluationModel::errorCountChanged , /* FATAL_WARNING */ &SecurityEvaluationModel::fatalWarningCountChanged, }}; SecurityEvaluationModelPrivate::SecurityEvaluationModelPrivate(Account* account, SecurityEvaluationModel* parent) : QObject(parent),q_ptr(parent), m_pAccount(account),m_isScheduled(false), m_CurrentSecurityLevel(SecurityEvaluationModel::SecurityLevel::NONE),m_pAccChecks(nullptr), m_SeverityCount{ /* UNSUPPORTED */ 0, /* INFORMATION */ 0, /* WARN1NG */ 0, /* ISSUE */ 0, /* ERROR */ 0, /* FATAL_WARNING */ 0, } { //Make sure the security level is updated if something change QObject::connect(parent,&SecurityEvaluationModel::layoutChanged , this,&SecurityEvaluationModelPrivate::update); QObject::connect(parent,&SecurityEvaluationModel::dataChanged , this,&SecurityEvaluationModelPrivate::update); QObject::connect(parent,&SecurityEvaluationModel::rowsInserted , this,&SecurityEvaluationModelPrivate::update); QObject::connect(parent,&SecurityEvaluationModel::rowsRemoved , this,&SecurityEvaluationModelPrivate::update); QObject::connect(parent,&SecurityEvaluationModel::modelReset , this,&SecurityEvaluationModelPrivate::update); } /******************************************************************************* * * * PrefixAndSeverityProxyModel * * * ******************************************************************************/ PrefixAndSeverityProxyModel::PrefixAndSeverityProxyModel(const QString& prefix, QAbstractItemModel* parent) : QIdentityProxyModel(parent),m_Name(prefix) { setSourceModel(parent); } ///It insert a second column with the source name int PrefixAndSeverityProxyModel::columnCount( const QModelIndex& parent) const { Q_UNUSED(parent) return parent.isValid() ? 0 : 3; } QModelIndex PrefixAndSeverityProxyModel::index( int row, int column, const QModelIndex& parent) const { if (column == 2) return createIndex(row,column); return QIdentityProxyModel::index(row,column,parent); } ///Map items and add elements QVariant PrefixAndSeverityProxyModel::data(const QModelIndex& index, int role) const { if (index.isValid()) { Certificate::Checks c = Certificate::Checks::HAS_PRIVATE_KEY; if (QIdentityProxyModel::data(index,(int)CertificateModel::Role::isCheck).toBool() == true) c = qvariant_cast<Certificate::Checks>(QIdentityProxyModel::data(index,(int)CertificateModel::Role::check)); else if (index.column() != 2) //That column doesn't exist in the source, the won't exist return QVariant(); switch (index.column()) { case (int)AccountChecksModel::Columns::MESSAGE: switch(role) { case Qt::DecorationRole: return GlobalInstances::pixmapManipulator().securityIssueIcon(index); case (int)SecurityEvaluationModel::Role::Severity: return QVariant::fromValue(SecurityEvaluationModelPrivate::certificateFlawSeverity[c]); case (int)SecurityEvaluationModel::Role::SecurityLevel: return QVariant::fromValue(SecurityEvaluationModelPrivate::maximumCertificateSecurityLevel[c]); } break; // case (int)AccountChecksModel::Columns::SOURCE: { switch(role) { case Qt::DisplayRole: return m_Name; case (int)SecurityEvaluationModel::Role::Severity: return QVariant::fromValue(SecurityEvaluationModelPrivate::certificateFlawSeverity[c]); case (int)SecurityEvaluationModel::Role::SecurityLevel: return QVariant::fromValue(SecurityEvaluationModelPrivate::maximumCertificateSecurityLevel[c]); } return QVariant(); } break; //Map source column 1 to 2 case (int)AccountChecksModel::Columns::RESULT: { const QModelIndex& srcIdx = sourceModel()->index(index.row(),1); c = qvariant_cast<Certificate::Checks>(srcIdx.data((int)CertificateModel::Role::check)); switch(role) { case (int)SecurityEvaluationModel::Role::Severity: return QVariant::fromValue(SecurityEvaluationModelPrivate::certificateFlawSeverity[c]); case (int)SecurityEvaluationModel::Role::SecurityLevel: return QVariant::fromValue(SecurityEvaluationModelPrivate::maximumCertificateSecurityLevel[c]); } return srcIdx.data(role); } } } return QIdentityProxyModel::data(index,role); } /******************************************************************************* * * * AccountChecksModel * * * ******************************************************************************/ AccountChecksModel::AccountChecksModel(const Account* a) : QAbstractTableModel(const_cast<Account*>(a)), m_pAccount(a) { update(); } QVariant AccountChecksModel::data( const QModelIndex& index, int role ) const { if ((!index.isValid()) || (index.row() < 0) || (index.row() >= enum_class_size<SecurityEvaluationModel::AccountSecurityChecks>()) ) return QVariant(); const SecurityEvaluationModel::AccountSecurityChecks f = static_cast<SecurityEvaluationModel::AccountSecurityChecks>(index.row()); switch(role) { case (int)SecurityEvaluationModel::Role::Severity: return QVariant::fromValue( m_lCachedResults[f] == Certificate::CheckValues::UNSUPPORTED ? SecurityEvaluationModel::Severity::UNSUPPORTED : SecurityEvaluationModelPrivate::flawSeverity[f] ); case (int)SecurityEvaluationModel::Role::SecurityLevel: return QVariant::fromValue( //If the check is unsupported then using "COMPLETE" won't affect the algorithm output // if n < current then n else current end will always be "current" when n == maximum m_lCachedResults[f] == Certificate::CheckValues::UNSUPPORTED ? SecurityEvaluationModel::SecurityLevel::COMPLETE : SecurityEvaluationModelPrivate::maximumSecurityLevel[f] ); } switch (index.column()) { case (int)AccountChecksModel::Columns::MESSAGE: switch(role) { case Qt::DisplayRole: return SecurityEvaluationModelPrivate::messages[index.row()]; case Qt::DecorationRole: return GlobalInstances::pixmapManipulator().securityIssueIcon(index); }; break; case (int)AccountChecksModel::Columns::SOURCE: switch(role) { case Qt::DisplayRole: return tr("Configuration"); }; break; case (int)AccountChecksModel::Columns::RESULT: switch(role) { case Qt::DisplayRole: if (m_lCachedResults[f] != Certificate::CheckValues::UNSUPPORTED) return m_lCachedResults[f] == Certificate::CheckValues::PASSED ? true : false; break; }; break; }; return QVariant(); } int AccountChecksModel::rowCount( const QModelIndex& parent ) const { return parent.isValid() ? 0 : enum_class_size<SecurityEvaluationModel::AccountSecurityChecks>(); } int AccountChecksModel::columnCount( const QModelIndex& parent ) const { return parent.isValid() ? 0 : enum_class_size<AccountChecksModel::Columns>(); } Qt::ItemFlags AccountChecksModel::flags( const QModelIndex& index) const { Q_UNUSED(index) return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } bool AccountChecksModel::setData( const QModelIndex& index, const QVariant &value, int role) { Q_UNUSED(index) Q_UNUSED(value) Q_UNUSED(role ) return false; } QHash<int,QByteArray> AccountChecksModel::roleNames() const { return {}; } //This could have been an inlined function too #define SET_CHECK_VALUE(check,condition) {Certificate::CheckValues c = Certificate::CheckValues::UNSUPPORTED;\ bool isSet = m_lCachedResults.isSet(check); if (isSet) c = m_lCachedResults[check]; m_lCachedResults.setAt( check ,\ condition? Certificate::CheckValues::PASSED : Certificate::CheckValues::FAILED);\ changed |= (!isSet) || c != m_lCachedResults[check];} void AccountChecksModel::update() { bool changed = false; // AccountSecurityChecks::SRTP_DISABLED SET_CHECK_VALUE(SecurityEvaluationModel::AccountSecurityChecks::SRTP_ENABLED , m_pAccount->isSrtpEnabled () || m_pAccount->protocol() == Account::Protocol::RING ); // AccountSecurityChecks::TLS_DISABLED SET_CHECK_VALUE( SecurityEvaluationModel::AccountSecurityChecks::TLS_ENABLED , m_pAccount->isTlsEnabled () ); // AccountSecurityChecks::CERTIFICATE_MISMATCH m_lCachedResults.setAt( SecurityEvaluationModel::AccountSecurityChecks::CERTIFICATE_MATCH , Certificate::CheckValues::UNSUPPORTED); //TODO // AccountSecurityChecks::OUTGOING_SERVER_MISMATCH m_lCachedResults.setAt( SecurityEvaluationModel::AccountSecurityChecks::OUTGOING_SERVER_MATCH , Certificate::CheckValues::UNSUPPORTED); //TODO // AccountSecurityChecks::VERIFY_INCOMING_DISABLED SET_CHECK_VALUE( SecurityEvaluationModel::AccountSecurityChecks::VERIFY_INCOMING_ENABLED , m_pAccount->isTlsVerifyServer () ); // AccountSecurityChecks::VERIFY_ANSWER_DISABLED SET_CHECK_VALUE( SecurityEvaluationModel::AccountSecurityChecks::VERIFY_ANSWER_ENABLED , m_pAccount->isTlsVerifyClient () ); // AccountSecurityChecks::REQUIRE_CERTIFICATE_DISABLED SET_CHECK_VALUE( SecurityEvaluationModel::AccountSecurityChecks::REQUIRE_CERTIFICATE_ENABLED , m_pAccount->isTlsRequireClientCertificate() ); // AccountSecurityChecks::MISSING_CERTIFICATE SET_CHECK_VALUE( SecurityEvaluationModel::AccountSecurityChecks::NOT_MISSING_CERTIFICATE , m_pAccount->tlsCertificate () ); // AccountSecurityChecks::MISSING_AUTHORITY SET_CHECK_VALUE( SecurityEvaluationModel::AccountSecurityChecks::NOT_MISSING_AUTHORITY , m_pAccount->tlsCaListCertificate () ); if (changed) emit dataChanged(index(0,2),index(rowCount()-1,2)); } #undef SET_CHECK_VALUE /******************************************************************************* * * * CombinaisonProxyModel * * * ******************************************************************************/ CombinaisonProxyModel::CombinaisonProxyModel(QAbstractItemModel* publicCert, QAbstractItemModel* caCert , QAbstractItemModel* account , QObject* parent ) : QAbstractTableModel(parent), m_lSources({publicCert,caCert,account}) { for (int i = 0; i < m_lSources.size(); i++) { const QAbstractItemModel* m = m_lSources[i]; if (m) { connect(m, &QAbstractItemModel::dataChanged, [this,i](const QModelIndex& tl, const QModelIndex& br) { int offset =0; for (int j = 0; j < i;j++) offset += sizes[j]; emit this->dataChanged(this->index(offset+tl.row(), br.column()), this->index(offset+br.row(), br.column())); }); } } } QVariant CombinaisonProxyModel::data( const QModelIndex& index, int role) const { const QAbstractItemModel* src = m_lSources[toModelIdx(index.row())]; //Role::Severity will give ::UNSUPPORTED (aka, 0) if a model is missing //this is done on purpose //All "groups" will have empty items for unsupported checks return index.isValid() && src ? src->index(fromFinal(index.row()),index.column()).data(role) : QVariant(); } int CombinaisonProxyModel::rowCount( const QModelIndex& parent) const { return parent.isValid() ? 0 : totalSize(); } int CombinaisonProxyModel::columnCount( const QModelIndex& parent) const { return parent.isValid() ? 0 : 3; } Qt::ItemFlags CombinaisonProxyModel::flags( const QModelIndex& index) const { Q_UNUSED(index) return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } bool CombinaisonProxyModel::setData( const QModelIndex& index, const QVariant &value, int role) { Q_UNUSED(index) Q_UNUSED(value) Q_UNUSED(role ) return false; } QHash<int,QByteArray> CombinaisonProxyModel::roleNames() const { return {}; } /******************************************************************************* * * * SecurityEvaluationModel * * * ******************************************************************************/ SecurityEvaluationModel::SecurityEvaluationModel(Account* account) : QSortFilterProxyModel(account), d_ptr(new SecurityEvaluationModelPrivate(account,this)) { Certificate* caCert = d_ptr->m_pAccount->tlsCaListCertificate (); Certificate* pkCert = d_ptr->m_pAccount->tlsCertificate (); SecurityEvaluationModelPrivate::getCertificateSeverityProxy(caCert); SecurityEvaluationModelPrivate::getCertificateSeverityProxy(pkCert); d_ptr->m_pAccChecks = new AccountChecksModel(account); d_ptr->update(); setSourceModel(new CombinaisonProxyModel(pkCert ? pkCert->d_ptr->m_pSeverityProxy : nullptr, caCert ? caCert->d_ptr->m_pSeverityProxy : nullptr, d_ptr->m_pAccChecks,this)); setSortRole((int)Role::Severity); } SecurityEvaluationModel::~SecurityEvaluationModel() { delete d_ptr; } bool SecurityEvaluationModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { const QModelIndex& idx = sourceModel()->index(source_row,0,source_parent); const QModelIndex& idx2 = sourceModel()->index(source_row,2,source_parent); const Severity s = qvariant_cast<Severity>(idx.data((int)SecurityEvaluationModel::Role::Severity)); return s != Severity::UNSUPPORTED && idx2.data(Qt::DisplayRole).toBool() == false; } QHash<int,QByteArray> SecurityEvaluationModel::roleNames() const { static QHash<int, QByteArray> roles = QAbstractItemModel::roleNames(); static bool initRoles = false; if (!initRoles) { initRoles = true; roles[(int)Role::Severity] = "Severity"; } return roles; } void SecurityEvaluationModelPrivate::update() { //As this can be called multiple time, only perform the checks once per event loop cycle if (!m_isScheduled) { m_pAccChecks->update(); #if QT_VERSION >= 0x050400 QTimer::singleShot(0,this,&SecurityEvaluationModelPrivate::updateReal); m_isScheduled = true; #else //Too bad for Qt < 5.3 users updateReal(); #endif } } QAbstractItemModel* SecurityEvaluationModelPrivate::getCertificateSeverityProxy(Certificate* c) { if (!c) return nullptr; if (!c->d_ptr->m_pSeverityProxy) c->d_ptr->m_pSeverityProxy = new PrefixAndSeverityProxyModel(tr("Authority" ),c->checksModel()); return c->d_ptr->m_pSeverityProxy; } SecurityEvaluationModel::SecurityLevel SecurityEvaluationModelPrivate::maxSecurityLevel(QAbstractItemModel* m, int* counter) { typedef SecurityEvaluationModel::Severity Severity ; typedef SecurityEvaluationModel::SecurityLevel SecurityLevel; SecurityLevel maxLevel = SecurityLevel::COMPLETE; for (int i=0; i < m->rowCount();i++) { const QModelIndex& idx = m->index(i,0); const Severity severity = qvariant_cast<Severity>( idx.data((int) SecurityEvaluationModel::Role::Severity) ); //Ignore items without severity const QVariant levelVariant = idx.data((int) SecurityEvaluationModel::Role::SecurityLevel ); const SecurityLevel level = levelVariant.canConvert<SecurityLevel>() ? qvariant_cast<SecurityLevel>( levelVariant ) : maxLevel; //Increment the count if (counter) counter[static_cast<int>(severity)]++; const bool forceIgnore = idx.data((int)CertificateModel::Role::requirePrivateKey).toBool(); //Update the maximum level maxLevel = level < maxLevel && !forceIgnore ? level : maxLevel; } return maxLevel; } void SecurityEvaluationModelPrivate::updateReal() { typedef SecurityEvaluationModel::Severity Severity ; typedef SecurityEvaluationModel::SecurityLevel SecurityLevel; int countCache[enum_class_size<SecurityEvaluationModel::Severity>()]; //Reset the counter for (const Severity s : EnumIterator<Severity>()) { countCache [(int)s] = m_SeverityCount[(int)s]; m_SeverityCount[(int)s] = 0 ; } SecurityLevel maxLevel = maxSecurityLevel(q_ptr, m_SeverityCount); //Notify for (const Severity s : EnumIterator<Severity>()) { if (countCache[(int)s] != m_SeverityCount[(int)s] && m_lSignalMap[s]) (q_ptr->*m_lSignalMap[s])(); } //Update the security level if (m_CurrentSecurityLevel != maxLevel) { m_CurrentSecurityLevel = maxLevel; emit q_ptr->securityLevelChanged(); } m_isScheduled = false; } QModelIndex SecurityEvaluationModel::getIndex(const SecurityFlaw* flaw) { return index(flaw->d_ptr->m_Row,0); } QList<SecurityFlaw*> SecurityEvaluationModel::currentFlaws() { return d_ptr->m_lCurrentFlaws; } SecurityEvaluationModel::SecurityLevel SecurityEvaluationModel::securityLevel() const { return d_ptr->m_CurrentSecurityLevel; } SecurityEvaluationModel::SecurityLevel SecurityEvaluationModelPrivate::certificateSecurityLevel(const Certificate* c, bool forceIgnorePrivateKey) { typedef SecurityEvaluationModel::SecurityLevel SecurityLevel; SecurityLevel maxLevelWithPriv = SecurityLevel::COMPLETE; SecurityLevel maxLevelWithoutPriv = SecurityLevel::COMPLETE; const bool ignorePrivateKey = forceIgnorePrivateKey || (c->requirePrivateKey() == false); if (c->d_ptr->m_hasLoadedSecurityLevel) { if (ignorePrivateKey) return c->d_ptr->m_SecurityLevelWithoutPriv; else return c->d_ptr->m_SecurityLevelWithPriv; } for (const Certificate::Checks check : EnumIterator<Certificate::Checks>()) { const bool relevant = relevantWithoutPrivateKey[check]; if (c->checkResult(check) == Certificate::CheckValues::FAILED) { if (relevant) { const SecurityLevel checkLevel = maximumCertificateSecurityLevel[check]; maxLevelWithoutPriv = checkLevel < maxLevelWithoutPriv ? checkLevel : maxLevelWithoutPriv; } const SecurityLevel checkLevel = maximumCertificateSecurityLevel[check]; maxLevelWithPriv = checkLevel < maxLevelWithPriv ? checkLevel : maxLevelWithPriv; } } c->d_ptr->m_hasLoadedSecurityLevel = true; c->d_ptr->m_SecurityLevelWithoutPriv = maxLevelWithoutPriv; c->d_ptr->m_SecurityLevelWithPriv = maxLevelWithPriv; return ignorePrivateKey ? c->d_ptr->m_SecurityLevelWithoutPriv : c->d_ptr->m_SecurityLevelWithPriv; } //Map the array to getters int SecurityEvaluationModel::informationCount () const { return d_ptr->m_SeverityCount[ (int)Severity::INFORMATION ]; } int SecurityEvaluationModel::warningCount () const { return d_ptr->m_SeverityCount[ (int)Severity::WARNING ]; } int SecurityEvaluationModel::issueCount () const { return d_ptr->m_SeverityCount[ (int)Severity::ISSUE ]; } int SecurityEvaluationModel::errorCount () const { return d_ptr->m_SeverityCount[ (int)Severity::ERROR ]; } int SecurityEvaluationModel::fatalWarningCount () const { return d_ptr->m_SeverityCount[ (int)Severity::FATAL_WARNING ]; } #include <securityevaluationmodel.moc>