File indexing completed on 2024-04-28 15:21:51

0001 /*  This file is part of the KDE project
0002     Copyright (C) 2010 Andreas Hartmetz <ahartmetz@gmail.com>
0003 
0004     This library is free software; you can redistribute it and/or
0005     modify it under the terms of the GNU Library General Public
0006     License as published by the Free Software Foundation; either
0007     version 2 of the License, or (at your option) any later version.
0008 
0009     This program is distributed in the hope that it will be useful,
0010     but WITHOUT ANY WARRANTY; without even the implied warranty of
0011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012     GNU General Public License for more details.
0013 
0014     You should have received a copy of the GNU General Public License
0015     along with this program; if not, write to the Free Software
0016     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0017     02110-1301, USA.
0018 */
0019 
0020 #include "cacertificatespage.h"
0021 #include "displaycertdialog_p.h"
0022 
0023 #include <ksslcertificatemanager.h>
0024 #include <ksslcertificatemanager_p.h>
0025 #include <klocalizedstring.h>
0026 
0027 #include <QDebug>
0028 
0029 #include <QFileDialog>
0030 #include <QList>
0031 #include <QSslCertificate>
0032 #include <QTreeWidgetItem>
0033 #include <QStandardItemModel>
0034 
0035 enum Columns {
0036     OrgCnColumn = 0,
0037     OrgUnitColumn,
0038     HiddenSortColumn
0039 };
0040 
0041 static QString nonemptyIssuer(const QSslCertificate &cert)
0042 {
0043     QString issuerText;
0044     static const QSslCertificate::SubjectInfo fields[3] = {
0045         QSslCertificate::Organization,
0046         QSslCertificate::CommonName,
0047         QSslCertificate::OrganizationalUnitName
0048     };
0049 
0050     QStringList info;
0051     for (int i = 0; i < 3; i++) {
0052         info = cert.issuerInfo(fields[i]);
0053         if (!info.isEmpty()) {
0054             issuerText = cert.issuerInfo(fields[i]).join(',');
0055 
0056             if (!issuerText.isEmpty()) {
0057                 return issuerText;
0058             }
0059         }
0060     }
0061     return issuerText;
0062 }
0063 
0064 class CaCertificateItem : public QTreeWidgetItem
0065 {
0066 public:
0067     CaCertificateItem(QTreeWidgetItem *parent, const QSslCertificate &cert, bool isEnabled)
0068         : QTreeWidgetItem(parent, m_type),
0069           m_cert(cert)
0070     {
0071         setEnabled(isEnabled);
0072     }
0073 
0074     QVariant data(int column, int role) const override
0075     {
0076         switch (role) {
0077         case Qt::DisplayRole:
0078             switch (column) {
0079             case OrgCnColumn:
0080             case HiddenSortColumn: {
0081                 QString subjectText;
0082                 QStringList info = m_cert.issuerInfo(QSslCertificate::CommonName);
0083                 if (!info.isEmpty()) {
0084                   subjectText = info.join(',');
0085                   if (column == HiddenSortColumn) {
0086                       return subjectText.toLower();
0087                   }
0088                 }
0089                 return subjectText;
0090             }
0091             case OrgUnitColumn:
0092                 return m_cert.issuerInfo(QSslCertificate::OrganizationalUnitName);
0093             }
0094         }
0095 
0096         return QTreeWidgetItem::data(column, role);
0097     }
0098 
0099     bool isEnabled() const
0100     {
0101         return data(OrgCnColumn, Qt::CheckStateRole).toInt() == Qt::Checked;
0102     }
0103 
0104     void setEnabled(bool enabled)
0105     {
0106         setData(OrgCnColumn, Qt::CheckStateRole, enabled ? Qt::Checked : Qt::Unchecked);
0107     }
0108 
0109     static const int m_type = QTreeWidgetItem::UserType;
0110     QSslCertificate m_cert;
0111 };
0112 
0113 CaCertificatesPage::CaCertificatesPage(QWidget *parent)
0114     : QWidget(parent),
0115       m_firstShowEvent(true),
0116       m_blockItemChanged(false)
0117 {
0118     m_ui.setupUi(this);
0119     connect(m_ui.displaySelection, SIGNAL(clicked()), SLOT(displaySelectionClicked()));
0120     connect(m_ui.disableSelection, SIGNAL(clicked()), SLOT(disableSelectionClicked()));
0121     connect(m_ui.enableSelection, SIGNAL(clicked()), SLOT(enableSelectionClicked()));
0122     connect(m_ui.removeSelection, SIGNAL(clicked()), SLOT(removeSelectionClicked()));
0123     connect(m_ui.add, SIGNAL(clicked()), SLOT(addCertificateClicked()));
0124     connect(m_ui.treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)),
0125             SLOT(itemChanged(QTreeWidgetItem*,int)));
0126     connect(m_ui.treeWidget, SIGNAL(itemSelectionChanged()),
0127             SLOT(itemSelectionChanged()));
0128 
0129     m_ui.treeWidget->setColumnCount(HiddenSortColumn + 1);
0130     m_ui.treeWidget->setColumnHidden(HiddenSortColumn, true);
0131 }
0132 
0133 void CaCertificatesPage::load()
0134 {
0135     m_ui.treeWidget->clear();
0136     m_ui.treeWidget->sortByColumn(-1);  // disable during mass insertion
0137     m_knownCertificates.clear();
0138 
0139     m_systemCertificatesParent = new QTreeWidgetItem(m_ui.treeWidget);
0140     m_systemCertificatesParent->setText(0, i18n("System certificates"));
0141     // make system certificates come first in the sorted view
0142     m_systemCertificatesParent->setText(HiddenSortColumn, QLatin1String("a"));
0143     m_systemCertificatesParent->setExpanded(true);
0144     m_systemCertificatesParent->setFlags(m_systemCertificatesParent->flags() & ~Qt::ItemIsSelectable);
0145 
0146     m_userCertificatesParent = new QTreeWidgetItem(m_ui.treeWidget);
0147     m_userCertificatesParent->setText(0, i18n("User-added certificates"));
0148     m_userCertificatesParent->setText(HiddenSortColumn, QLatin1String("b"));
0149     m_userCertificatesParent->setExpanded(true);
0150     m_userCertificatesParent->setFlags(m_userCertificatesParent->flags() & ~Qt::ItemIsSelectable);
0151 
0152     QList<KSslCaCertificate> caCerts = _allKsslCaCertificates(KSslCertificateManager::self());
0153     // qDebug() << "# certs:" << caCerts.count();
0154     foreach (const KSslCaCertificate &caCert, caCerts) {
0155         addCertificateItem(caCert);
0156     }
0157 
0158     m_ui.treeWidget->sortByColumn(HiddenSortColumn, Qt::AscendingOrder);
0159 }
0160 
0161 void CaCertificatesPage::showEvent(QShowEvent *event)
0162 {
0163     if (m_firstShowEvent) {
0164         // TODO use QTextMetrics
0165         m_ui.treeWidget->setColumnWidth(OrgCnColumn, 420);
0166         m_firstShowEvent = false;
0167     }
0168     QWidget::showEvent(event);
0169 }
0170 
0171 void CaCertificatesPage::save()
0172 {
0173     QList<KSslCaCertificate> newState;
0174 
0175     KSslCaCertificate::Store store = KSslCaCertificate::SystemStore;
0176     QTreeWidgetItem *grandParent = m_systemCertificatesParent;
0177 
0178     for (int i = 0; i < 2; i++) {
0179         for (int j = 0; j < grandParent->childCount(); j++) {
0180 
0181             QTreeWidgetItem *parentItem = grandParent->child(j);
0182             for (int k = 0; k < parentItem->childCount(); k++) {
0183                 CaCertificateItem *item = static_cast<CaCertificateItem *>(parentItem->child(k));
0184                 newState += KSslCaCertificate(item->m_cert, store, !item->isEnabled());
0185             }
0186         }
0187         store = KSslCaCertificate::UserStore;
0188         grandParent = m_userCertificatesParent;
0189     }
0190 
0191     // qDebug() << "# certs:" << newState.count();
0192     _setAllKsslCaCertificates(KSslCertificateManager::self(), newState);
0193     emit changed(false);
0194 }
0195 
0196 void CaCertificatesPage::defaults()
0197 {
0198     //### is that all?
0199     load();
0200     emit changed(false);
0201 }
0202 
0203 // private slot
0204 void CaCertificatesPage::itemSelectionChanged()
0205 {
0206     // qDebug() << m_ui.treeWidget->selectionModel()->hasSelection();
0207     int numRemovable = 0;
0208     int numEnabled = 0;
0209     int numDisplayable = 0;
0210     foreach (const QTreeWidgetItem *twItem, m_ui.treeWidget->selectedItems()) {
0211         const CaCertificateItem *item = dynamic_cast<const CaCertificateItem *>(twItem);
0212         Q_ASSERT(item);
0213         if (item) {
0214             numDisplayable++;
0215             if (item->parent()->parent() == m_userCertificatesParent) {
0216                 numRemovable++;
0217             }
0218             if (item->isEnabled()) {
0219                 numEnabled++;
0220             }
0221         }
0222     }
0223     m_ui.displaySelection->setEnabled(numDisplayable);
0224     m_ui.removeSelection->setEnabled(numRemovable);
0225     m_ui.disableSelection->setEnabled(numEnabled);
0226     m_ui.enableSelection->setEnabled(numDisplayable > numEnabled); // the rest is disabled
0227 }
0228 
0229 // private slot
0230 void CaCertificatesPage::displaySelectionClicked()
0231 {
0232     QList<QSslCertificate> certs;
0233     foreach (const QTreeWidgetItem *twItem, m_ui.treeWidget->selectedItems()) {
0234         const CaCertificateItem *item = dynamic_cast<const CaCertificateItem *>(twItem);
0235         Q_ASSERT(item);
0236         if (item) {
0237             certs += item->m_cert;
0238         }
0239     }
0240     DisplayCertDialog dc(this);
0241     dc.setCertificates(certs);
0242     dc.exec();
0243 }
0244 
0245 // private slot
0246 void CaCertificatesPage::disableSelectionClicked()
0247 {
0248     enableDisableSelectionClicked(false);
0249 }
0250 
0251 // private slot
0252 void CaCertificatesPage::enableSelectionClicked()
0253 {
0254     enableDisableSelectionClicked(true);
0255 }
0256 
0257 void CaCertificatesPage::enableDisableSelectionClicked(bool isEnable)
0258 {
0259     const bool prevBlockItemChanged = m_blockItemChanged;
0260     m_blockItemChanged = true;
0261     foreach (QTreeWidgetItem *twItem, m_ui.treeWidget->selectedItems()) {
0262         CaCertificateItem *item = dynamic_cast<CaCertificateItem *>(twItem);
0263         Q_ASSERT(item);
0264         if (item) {
0265             item->setEnabled(isEnable);
0266         }
0267     }
0268     emit changed(true);
0269     m_blockItemChanged = prevBlockItemChanged;
0270     // now make sure that the buttons are enabled as appropriate
0271     itemSelectionChanged();
0272 }
0273 
0274 // private slot
0275 void CaCertificatesPage::removeSelectionClicked()
0276 {
0277     bool didRemove = false;
0278     foreach (QTreeWidgetItem *twItem, m_ui.treeWidget->selectedItems()) {
0279         const CaCertificateItem *item = dynamic_cast<const CaCertificateItem *>(twItem);
0280         Q_ASSERT(item);
0281         if (!item || item->parent()->parent() != m_userCertificatesParent) {
0282             continue;
0283         }
0284         QTreeWidgetItem *parent = item->parent();
0285         m_knownCertificates.remove(item->m_cert.digest().toHex());
0286         delete item;
0287         didRemove = true;
0288         if (parent->childCount() == 0) {
0289             delete parent;
0290         }
0291     }
0292     if (didRemove) {
0293         emit changed(true);
0294     }
0295 }
0296 
0297 // private slot
0298 void CaCertificatesPage::addCertificateClicked()
0299 {
0300     QStringList mimeTypes(QStringLiteral("application/x-x509-ca-cert"));
0301 
0302     QFileDialog *fdlg = new QFileDialog(this, i18n("Pick Certificates"));
0303     fdlg->setMimeTypeFilters(mimeTypes);
0304     fdlg->setFileMode(QFileDialog::ExistingFiles);
0305 
0306     fdlg->exec();
0307 
0308     const QStringList certFiles = fdlg->selectedFiles();
0309 
0310     fdlg->deleteLater();
0311 
0312     QList<QSslCertificate> certs;
0313     foreach (const QString &certFile, certFiles) {
0314         // trying both formats is easiest to program and most user-friendly if somewhat sloppy
0315         const int prevCertCount = certs.count();
0316         certs += QSslCertificate::fromPath(certFile, QSsl::Pem);
0317         if (prevCertCount == certs.count()) {
0318             certs += QSslCertificate::fromPath(certFile, QSsl::Der);
0319         }
0320         if (prevCertCount == certs.count()) {
0321             // qDebug() << "failed to load certificate file" << certFile;
0322         }
0323     }
0324 
0325     bool didAddCertificates = false;
0326     foreach (const QSslCertificate &cert, certs) {
0327         KSslCaCertificate caCert(cert, KSslCaCertificate::UserStore, false);
0328         if (!addCertificateItem(caCert)) {
0329             // ### tell the user?
0330         } else {
0331             didAddCertificates = true;
0332         }
0333     }
0334     if (didAddCertificates) {
0335         emit changed(true);
0336     }
0337 }
0338 
0339 // private slot
0340 void CaCertificatesPage::itemChanged(QTreeWidgetItem *item, int column)
0341 {
0342     Q_UNUSED(item)
0343     Q_UNUSED(column)
0344 
0345     if (m_blockItemChanged) {
0346         return;
0347     }
0348     // qDebug();
0349     // we could try to emit changed(false) if everything was changed back to status quo
0350 
0351     // a click on the checkbox of an unselected item first invokes itemSelectionChanged(),
0352     // then itemChanged(). we'll have to rerun the checks in itemSelectionChanged().
0353     itemSelectionChanged();
0354     emit changed(true);
0355 }
0356 
0357 static QTreeWidgetItem *findImmediateChild(QTreeWidgetItem *parent, const QString &issuerText)
0358 {
0359     for (int i = 0; i < parent->childCount(); i ++) {
0360         QTreeWidgetItem *candidate = parent->child(i);
0361         if (candidate->text(OrgCnColumn) == issuerText) {
0362             return candidate;
0363         }
0364     }
0365     return nullptr;
0366 }
0367 
0368 bool CaCertificatesPage::addCertificateItem(const KSslCaCertificate &caCert)
0369 {
0370     if (m_knownCertificates.contains(caCert.certHash)) {
0371         // qDebug() << "CaCertificatesPage::addCertificateItem(): refusing duplicate";
0372         return false;
0373     }
0374     const bool prevBlockItemChanged = m_blockItemChanged;
0375     m_blockItemChanged = true;
0376     QTreeWidgetItem *grandParent = caCert.store == KSslCaCertificate::SystemStore ?
0377                                    m_systemCertificatesParent : m_userCertificatesParent;
0378     const QString issuerOrganization = nonemptyIssuer(caCert.cert);
0379 
0380     QTreeWidgetItem *parent = findImmediateChild(grandParent, issuerOrganization);
0381     if (!parent) {
0382         parent = new QTreeWidgetItem(grandParent);
0383         parent->setText(OrgCnColumn, issuerOrganization);
0384         parent->setText(HiddenSortColumn, issuerOrganization.toLower());
0385         parent->setExpanded(true);
0386         parent->setFlags(parent->flags() & ~Qt::ItemIsSelectable);
0387     }
0388 
0389     (void) new CaCertificateItem(parent, caCert.cert, !caCert.isBlacklisted);
0390     m_knownCertificates.insert(caCert.certHash);
0391     m_blockItemChanged = prevBlockItemChanged;
0392     return true;
0393 }
0394 
0395 #include "moc_cacertificatespage.cpp"