File indexing completed on 2023-09-24 05:08:01
0001 /* 0002 * Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> 0003 * @author Andre Moreira Magalhaes <andre.magalhaes@collabora.co.uk> 0004 * Copyright (C) 2013 Dan Vrátil <dvratil@redhat.com> 0005 * 0006 * This library is free software; you can redistribute it and/or 0007 * modify it under the terms of the GNU Lesser General Public 0008 * License as published by the Free Software Foundation; either 0009 * version 2.1 of the License, or (at your option) any later version. 0010 * 0011 * This library is distributed in the hope that it will be useful, 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0014 * Lesser General Public License for more details. 0015 * 0016 * You should have received a copy of the GNU Lesser General Public 0017 * License along with this library; if not, write to the Free Software 0018 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 0019 */ 0020 0021 #include "tls-cert-verifier-op.h" 0022 0023 #include <TelepathyQt/PendingVariantMap> 0024 0025 #include <KMessageBox> 0026 #include <KLocalizedString> 0027 0028 #include <QDebug> 0029 #include <QSslCertificate> 0030 #include <QSslCipher> 0031 0032 #include <ksslcertificatemanager.h> 0033 #include <ksslinfodialog.h> 0034 0035 #include <QtCrypto> 0036 0037 TlsCertVerifierOp::TlsCertVerifierOp(const Tp::AccountPtr &account, 0038 const Tp::ConnectionPtr &connection, 0039 const Tp::ChannelPtr &channel) 0040 : Tp::PendingOperation(channel), 0041 m_account(account), 0042 m_connection(connection), 0043 m_channel(channel) 0044 { 0045 QDBusObjectPath certificatePath = qdbus_cast<QDBusObjectPath>(channel->immutableProperties().value( 0046 TP_QT_IFACE_CHANNEL_TYPE_SERVER_TLS_CONNECTION + QLatin1String(".ServerCertificate"))); 0047 m_hostname = qdbus_cast<QString>(channel->immutableProperties().value( 0048 TP_QT_IFACE_CHANNEL_TYPE_SERVER_TLS_CONNECTION + QLatin1String(".Hostname"))); 0049 m_referenceIdentities = qdbus_cast<QStringList>(channel->immutableProperties().value( 0050 TP_QT_IFACE_CHANNEL_TYPE_SERVER_TLS_CONNECTION + QLatin1String(".ReferenceIdentities"))); 0051 0052 m_authTLSCertificateIface = new Tp::Client::AuthenticationTLSCertificateInterface( 0053 channel->dbusConnection(), channel->busName(), certificatePath.path()); 0054 connect(m_authTLSCertificateIface->requestAllProperties(), 0055 SIGNAL(finished(Tp::PendingOperation*)), 0056 SLOT(gotProperties(Tp::PendingOperation*))); 0057 } 0058 0059 TlsCertVerifierOp::~TlsCertVerifierOp() 0060 { 0061 } 0062 0063 void TlsCertVerifierOp::gotProperties(Tp::PendingOperation *op) 0064 { 0065 if (op->isError()) { 0066 qWarning() << "Unable to retrieve properties from AuthenticationTLSCertificate object at" << 0067 m_authTLSCertificateIface->path(); 0068 m_channel->requestClose(); 0069 setFinishedWithError(op->errorName(), op->errorMessage()); 0070 return; 0071 } 0072 0073 // everything ok, we can return from handleChannels now 0074 Q_EMIT ready(this); 0075 0076 Tp::PendingVariantMap *pvm = qobject_cast<Tp::PendingVariantMap*>(op); 0077 QVariantMap props = qdbus_cast<QVariantMap>(pvm->result()); 0078 m_certType = qdbus_cast<QString>(props.value(QLatin1String("CertificateType"))); 0079 m_certData = qdbus_cast<CertificateDataList>(props.value(QLatin1String("CertificateChainData"))); 0080 0081 //compare returns 0 on match. We run this if cert type does not match x509 or "x509" 0082 //we also seem to need to check for "x509" and x509. 0083 if (m_certType.compare(QLatin1String("\"x509\""), Qt::CaseInsensitive) != 0 && 0084 m_certType.compare(QLatin1String("x509"), Qt::CaseInsensitive) != 0) { 0085 Tp::TLSCertificateRejectionList rejections; 0086 m_authTLSCertificateIface->Reject(rejections); 0087 m_channel->requestClose(); 0088 setFinishedWithError(QLatin1String("Cert.Unknown"), 0089 i18n("Invalid certificate type %1", m_certType)); 0090 return; 0091 } 0092 0093 // Initialize QCA module 0094 QCA::Initializer initializer; 0095 0096 if (!QCA::isSupported("cert")) { 0097 Tp::TLSCertificateRejectionList rejections; 0098 m_authTLSCertificateIface->Reject(rejections); 0099 m_channel->requestClose(); 0100 setFinishedWithError( 0101 QLatin1String("Cert.NoPlugin"), 0102 i18n("The SSL/TLS support plugin is not available. " 0103 "Certificate validation cannot be done.")); 0104 return; 0105 } 0106 0107 QCA::CertificateChain chain; 0108 Q_FOREACH (const QByteArray &data, m_certData) { 0109 chain << QCA::Certificate::fromDER(data); 0110 } 0111 0112 if (verifyCertChain(chain)) { 0113 m_authTLSCertificateIface->Accept().waitForFinished(); 0114 setFinished(); 0115 } else { 0116 Tp::TLSCertificateRejectionList rejections; 0117 m_authTLSCertificateIface->Reject(rejections); 0118 m_channel->requestClose(); 0119 setFinishedWithError(QLatin1String("Cert.Untrusted"), 0120 i18n("Certificate rejected by the user")); 0121 } 0122 } 0123 0124 bool TlsCertVerifierOp::verifyCertChain(const QCA::CertificateChain& chain) 0125 { 0126 const QList<QSslCertificate> primary = QSslCertificate::fromData(chain.primary().toDER(), QSsl::Der); 0127 KSslCertificateManager *const cm = KSslCertificateManager::self(); 0128 KSslCertificateRule rule = cm->rule(primary.first(), m_hostname); 0129 0130 // Find all errors then are not ignored by the rule 0131 QList<KSslError> errors; 0132 0133 QCA::Validity validity = chain.validate(CACollection()); 0134 if (validity != QCA::ValidityGood) { 0135 KSslError::Error error = validityToError(validity); 0136 if (!rule.ignoredErrors().contains(error)) { 0137 errors << KSslError(error); 0138 } 0139 } 0140 0141 // If all errors are ignored, just accept 0142 if (errors.isEmpty()) { 0143 return true; 0144 } 0145 0146 QString message = i18n("The server failed the authenticity check (%1).\n\n", m_hostname); 0147 Q_FOREACH(const KSslError &error, errors) { 0148 message.append(error.errorString()); 0149 message.append(QLatin1Char('\n')); 0150 } 0151 0152 int msgResult; 0153 do { 0154 msgResult = KMessageBox::warningYesNoCancel(0, 0155 message, 0156 i18n("Server Authentication"), 0157 KGuiItem(i18n("&Details"), QLatin1String("dialog-information")), // yes 0158 KGuiItem(i18n("Co&ntinue"), QLatin1String("arrow-right")), // no 0159 KGuiItem(i18n("&Cancel"), QLatin1String("dialog-cancel"))); 0160 if (msgResult == KMessageBox::Yes) { 0161 showSslDialog(chain, errors); 0162 } else if (msgResult == KMessageBox::Cancel) { 0163 return false; // reject 0164 } 0165 // Fall through on KMessageBox::No 0166 } while (msgResult == KMessageBox::Yes); 0167 0168 // Save the user's choice to ignore the SSL errors. 0169 msgResult = KMessageBox::warningYesNo( 0170 0, 0171 i18n("Would you like to accept this " 0172 "certificate forever without " 0173 "being prompted?"), 0174 i18n("Server Authentication"), 0175 KGuiItem(i18n("&Forever"), QLatin1String("flag-green")), 0176 KGuiItem(i18n("&Current Session only"), QLatin1String("chronometer"))); 0177 QDateTime ruleExpiry = QDateTime::currentDateTime(); 0178 if (msgResult == KMessageBox::Yes) { 0179 // Accept forever ("for a very long time") 0180 ruleExpiry = ruleExpiry.addYears(1000); 0181 } else { 0182 // Accept "for a short time", half an hour. 0183 ruleExpiry = ruleExpiry.addSecs(30*60); 0184 } 0185 0186 rule.setExpiryDateTime(ruleExpiry); 0187 rule.setIgnoredErrors(errors); 0188 cm->setRule(rule); 0189 0190 return true; 0191 } 0192 0193 void TlsCertVerifierOp::showSslDialog(const QCA::CertificateChain &chain, const QList<KSslError> &errors) const 0194 { 0195 QString errorStr; 0196 Q_FOREACH (const KSslError &error, errors) { 0197 errorStr += QString::number(static_cast<int>(error.error())) + QLatin1Char('\t'); 0198 errorStr += QLatin1Char('\n'); 0199 } 0200 errorStr.chop(1); 0201 0202 // No way to tell whether QSsl::TlsV1 or QSsl::TlsV1Ssl3 0203 KSslCipher cipher = QSslCipher(QLatin1String("TLS"), QSsl::TlsV1_0); 0204 QString sslCipher = cipher.encryptionMethod() + QLatin1Char('\n'); 0205 sslCipher += cipher.authenticationMethod() + QLatin1Char('\n'); 0206 sslCipher += cipher.keyExchangeMethod() + QLatin1Char('\n'); 0207 sslCipher += cipher.digestMethod(); 0208 0209 const QList<QSslCertificate> qchain = chainToList(chain); 0210 QPointer<KSslInfoDialog> dialog(new KSslInfoDialog(0)); 0211 dialog->setSslInfo(qchain, 0212 QString(), // we don't know the IP 0213 m_hostname, // the URL 0214 QLatin1String("TLS"), 0215 sslCipher, 0216 cipher.usedBits(), 0217 cipher.supportedBits(), 0218 KSslInfoDialog::errorsFromString(errorStr)); 0219 0220 dialog->exec(); 0221 delete dialog; 0222 } 0223 0224 KSslError::Error TlsCertVerifierOp::validityToError(QCA::Validity validity) const 0225 { 0226 switch (validity) { 0227 case QCA::ValidityGood: 0228 return KSslError::NoError; 0229 case QCA::ErrorRejected: 0230 return KSslError::RejectedCertificate; 0231 case QCA::ErrorUntrusted: 0232 return KSslError::UntrustedCertificate; 0233 case QCA::ErrorSignatureFailed: 0234 return KSslError::CertificateSignatureFailed; 0235 case QCA::ErrorInvalidCA: 0236 return KSslError::InvalidCertificateAuthorityCertificate; 0237 case QCA::ErrorInvalidPurpose: 0238 return KSslError::InvalidCertificatePurpose; 0239 case QCA::ErrorSelfSigned: 0240 return KSslError::SelfSignedCertificate; 0241 case QCA::ErrorRevoked: 0242 return KSslError::RevokedCertificate; 0243 case QCA::ErrorPathLengthExceeded: 0244 return KSslError::PathLengthExceeded; 0245 case QCA::ErrorExpired: // fall-through 0246 case QCA::ErrorExpiredCA: 0247 return KSslError::ExpiredCertificate; 0248 case QCA::ErrorValidityUnknown: 0249 return KSslError::UnknownError; 0250 } 0251 0252 return KSslError::UnknownError; 0253 } 0254 0255 QCA::CertificateCollection TlsCertVerifierOp::CACollection() const 0256 { 0257 QList<QSslCertificate> certs = KSslCertificateManager::self()->caCertificates(); 0258 QCA::CertificateCollection collection; 0259 0260 Q_FOREACH(const QSslCertificate &cert, certs) { 0261 collection.addCertificate(QCA::Certificate::fromDER(cert.toDer())); 0262 } 0263 0264 return collection; 0265 } 0266 0267 QList< QSslCertificate > TlsCertVerifierOp::chainToList(const QCA::CertificateChain& chain) const 0268 { 0269 QList<QSslCertificate> certs; 0270 Q_FOREACH(const QCA::Certificate &cert, chain) { 0271 certs << QSslCertificate::fromData(cert.toDER(), QSsl::Der); 0272 } 0273 0274 return certs; 0275 }