File indexing completed on 2023-05-30 11:40:09

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 }