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"