File indexing completed on 2024-06-23 04:03:36

0001 /*
0002  * tlshandler.cpp - abstract wrapper for TLS
0003  * Copyright (C) 2003  Justin Karneges
0004  *
0005  * This library is free software; you can redistribute it and/or
0006  * modify it under the terms of the GNU Lesser General Public
0007  * License as published by the Free Software Foundation; either
0008  * either version 2
0009    of the License, or (at your option) any later version.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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
0019  *
0020  */
0021 
0022 #include "xmpp.h"
0023 
0024 #include <QTimer>
0025 #include <QRegExp>
0026 #include "qca.h"
0027 
0028 using namespace XMPP;
0029 
0030 // FIXME: remove this code once qca cert host checking works ...
0031 using namespace QCA;
0032 #include <QUrl>
0033 
0034 // ip address string to binary (msb), adapted from jdns (adapted from qt)
0035 // return: size 4 = ipv4, size 16 = ipv6, size 0 = error
0036 static QByteArray ipaddr_str2bin(const QString &str)
0037 {
0038     // ipv6
0039     if(str.contains(':'))
0040     {
0041         QStringList parts = str.split(':', Qt::KeepEmptyParts);
0042         if(parts.count() < 3 || parts.count() > 8)
0043             return QByteArray();
0044 
0045         QByteArray ipv6(16, 0);
0046         int at = 16;
0047         int fill = 9 - parts.count();
0048         for(int n = parts.count() - 1; n >= 0; --n)
0049         {
0050             if(at <= 0)
0051                 return QByteArray();
0052 
0053             if(parts[n].isEmpty())
0054             {
0055                 if(n == parts.count() - 1)
0056                 {
0057                     if(!parts[n - 1].isEmpty())
0058                         return QByteArray();
0059                     ipv6[--at] = 0;
0060                     ipv6[--at] = 0;
0061                 }
0062                 else if(n == 0)
0063                 {
0064                     if(!parts[n + 1].isEmpty())
0065                         return QByteArray();
0066                     ipv6[--at] = 0;
0067                     ipv6[--at] = 0;
0068                 }
0069                 else
0070                 {
0071                     for(int i = 0; i < fill; ++i)
0072                     {
0073                         if(at <= 0)
0074                             return QByteArray();
0075                         ipv6[--at] = 0;
0076                         ipv6[--at] = 0;
0077                     }
0078                 }
0079             }
0080             else
0081             {
0082                 if(parts[n].indexOf('.') == -1)
0083                 {
0084                     bool ok;
0085                     int x = parts[n].toInt(&ok, 16);
0086                     if(!ok || x < 0 || x > 0xffff)
0087                         return QByteArray();
0088                     ipv6[--at] = x & 0xff;
0089                     ipv6[--at] = (x >> 8) & 0xff;
0090                 }
0091                 else
0092                 {
0093                     if(n != parts.count() - 1)
0094                         return QByteArray();
0095 
0096                     QByteArray buf = ipaddr_str2bin(parts[n]);
0097                     if(buf.isEmpty())
0098                         return QByteArray();
0099 
0100                     ipv6[--at] = buf[3];
0101                     ipv6[--at] = buf[2];
0102                     ipv6[--at] = buf[1];
0103                     ipv6[--at] = buf[0];
0104                     --fill;
0105                 }
0106             }
0107         }
0108 
0109         return ipv6;
0110     }
0111     else if(str.contains('.'))
0112     {
0113         QStringList parts = str.split('.', Qt::KeepEmptyParts);
0114         if(parts.count() != 4)
0115             return QByteArray();
0116 
0117         QByteArray out(4, 0);
0118         for(int n = 0; n < 4; ++n)
0119         {
0120             bool ok;
0121             int x = parts[n].toInt(&ok);
0122             if(!ok || x < 0 || x > 0xff)
0123                 return QByteArray();
0124             out[n] = (unsigned char)x;
0125         }
0126         return out;
0127     }
0128     else
0129         return QByteArray();
0130 }
0131 
0132 // acedomain must be all lowercase, with no trailing dot or wildcards
0133 static bool cert_match_domain(const QString &certname, const QString &acedomain)
0134 {
0135     // KSSL strips start/end whitespace, even though such whitespace is
0136     //   probably not legal anyway. (compat)
0137     QString name = certname.trimmed();
0138 
0139     // KSSL strips trailing dot, even though the dot is probably not
0140     //   legal anyway. (compat)
0141     if(name.length() > 0 && name[name.length()-1] == '.')
0142         name.truncate(name.length()-1);
0143 
0144     // after our compatibility modifications, make sure the name isn't
0145     //   empty.
0146     if(name.isEmpty())
0147         return false;
0148 
0149     // lowercase, for later performing case insensitive matching
0150     name = name.toLower();
0151 
0152     // ensure the cert field contains valid characters only
0153     if(QRegExp("[^a-z0-9\\.\\*\\-]").indexIn(name) >= 0)
0154         return false;
0155 
0156     // hack into parts, and require at least 1 part
0157     QStringList parts_name = name.split('.', Qt::KeepEmptyParts);
0158     if(parts_name.isEmpty())
0159         return false;
0160 
0161     // KSSL checks to make sure the last two parts don't contain
0162     //   wildcards.  I don't know where it is written that this
0163     //   should be done, but for compat sake we'll do it.
0164     if(parts_name[parts_name.count()-1].contains('*'))
0165         return false;
0166     if(parts_name.count() >= 2 && parts_name[parts_name.count()-2].contains('*'))
0167         return false;
0168 
0169     QStringList parts_compare = acedomain.split('.', Qt::KeepEmptyParts);
0170     if(parts_compare.isEmpty())
0171         return false;
0172 
0173     // don't allow empty parts
0174     foreach(const QString &s, parts_name)
0175     {
0176         if(s.isEmpty())
0177             return false;
0178     }
0179     foreach(const QString &s, parts_compare)
0180     {
0181         if(s.isEmpty())
0182             return false;
0183     }
0184 
0185     // RFC2818: "Names may contain the wildcard character * which is
0186     //   considered to match any single domain name component or
0187     //   component fragment. E.g., *.a.com matches foo.a.com but not
0188     //   bar.foo.a.com. f*.com matches foo.com but not bar.com."
0189     //
0190     // This means that for the domain to match it must have the
0191     //   same number of components, wildcards or not.  If there are
0192     //   wildcards, their scope must only be within the component
0193     //   they reside in.
0194     //
0195     // First, make sure the number of parts is equal.
0196     if(parts_name.count() != parts_compare.count())
0197         return false;
0198 
0199     // Now compare each part
0200     for(int n = 0; n < parts_name.count(); ++n)
0201     {
0202         const QString &p1 = parts_name[n];
0203         const QString &p2 = parts_compare[n];
0204 
0205         if(!QRegExp(p1, Qt::CaseSensitive, QRegExp::Wildcard).exactMatch(p2))
0206             return false;
0207     }
0208 
0209     return true;
0210 }
0211 
0212 // ipaddress must be an ipv4 or ipv6 address in binary format
0213 static bool cert_match_ipaddress(const QString &certname, const QByteArray &ipaddress)
0214 {
0215     // KSSL strips start/end whitespace, even though such whitespace is
0216     //   probably not legal anyway. (compat)
0217     QString name = certname.trimmed();
0218 
0219     // KSSL accepts IPv6 in brackets, which is usually done for URIs, but
0220     //   IMO sounds very strange for a certificate.  We'll follow this
0221     //   behavior anyway. (compat)
0222     if(name.length() >= 2 && name[0] == '[' && name[name.length()-1] == ']')
0223         name = name.mid(1, name.length() - 2); // chop off brackets
0224 
0225     // after our compatibility modifications, make sure the name isn't
0226     //   empty.
0227     if(name.isEmpty())
0228         return false;
0229 
0230     // convert to binary form
0231     QByteArray addr = ipaddr_str2bin(name);
0232     if(addr.isEmpty())
0233         return false;
0234 
0235     // not the same?
0236     if(addr != ipaddress)
0237         return false;
0238 
0239     return true;
0240 }
0241 
0242 static bool matchesHostName(const QCA::Certificate &cert, const QString &host)
0243 {
0244     QByteArray ipaddr = ipaddr_str2bin(host);
0245     if(!ipaddr.isEmpty()) // ip address
0246     {
0247         // check iPAddress
0248         foreach(const QString &s, cert.subjectInfo().values(IPAddress))
0249         {
0250             if(cert_match_ipaddress(s, ipaddr))
0251                 return true;
0252         }
0253 
0254         // check dNSName
0255         foreach(const QString &s, cert.subjectInfo().values(DNS))
0256         {
0257             if(cert_match_ipaddress(s, ipaddr))
0258                 return true;
0259         }
0260 
0261         // check commonName
0262         foreach(const QString &s, cert.subjectInfo().values(CommonName))
0263         {
0264             if(cert_match_ipaddress(s, ipaddr))
0265                 return true;
0266         }
0267     }
0268     else // domain
0269     {
0270         // lowercase
0271         QString name = host.toLower();
0272 
0273         // ACE
0274         name = QString::fromLatin1(QUrl::toAce(name));
0275 
0276         // don't allow wildcards in the comparison host
0277         if(name.contains('*'))
0278             return false;
0279 
0280         // strip out trailing dot
0281         if(name.length() > 0 && name[name.length()-1] == '.')
0282             name.truncate(name.length()-1);
0283 
0284         // make sure the name is not empty after our modifications
0285         if(name.isEmpty())
0286             return false;
0287 
0288         // check dNSName
0289         foreach(const QString &s, cert.subjectInfo().values(DNS))
0290         {
0291             if(cert_match_domain(s, name))
0292                 return true;
0293         }
0294 
0295         // check commonName
0296         foreach(const QString &s, cert.subjectInfo().values(CommonName))
0297         {
0298             if(cert_match_domain(s, name))
0299                 return true;
0300         }
0301     }
0302 
0303     return false;
0304 }
0305 
0306 //----------------------------------------------------------------------------
0307 // TLSHandler
0308 //----------------------------------------------------------------------------
0309 TLSHandler::TLSHandler(QObject *parent)
0310 :QObject(parent)
0311 {
0312 }
0313 
0314 TLSHandler::~TLSHandler()
0315 {
0316 }
0317 
0318 
0319 //----------------------------------------------------------------------------
0320 // QCATLSHandler
0321 //----------------------------------------------------------------------------
0322 class QCATLSHandler::Private
0323 {
0324 public:
0325     QCA::TLS *tls;
0326     int state, err;
0327     QString host;
0328     bool internalHostMatch;
0329 };
0330 
0331 QCATLSHandler::QCATLSHandler(QCA::TLS *parent)
0332 :TLSHandler(parent)
0333 {
0334     d = new Private;
0335     d->tls = parent;
0336     connect(d->tls, &TLS::handshaken, this, &QCATLSHandler::tls_handshaken);
0337     connect(d->tls, &SecureLayer::readyRead, this, &QCATLSHandler::tls_readyRead);
0338     connect(d->tls, &SecureLayer::readyReadOutgoing, this, &QCATLSHandler::tls_readyReadOutgoing);
0339     connect(d->tls, &SecureLayer::closed, this, &QCATLSHandler::tls_closed);
0340     connect(d->tls, &SecureLayer::error, this, &QCATLSHandler::tls_error);
0341     d->state = 0;
0342     d->err = -1;
0343     d->internalHostMatch = false;
0344 }
0345 
0346 QCATLSHandler::~QCATLSHandler()
0347 {
0348     delete d;
0349 }
0350 
0351 void QCATLSHandler::setXMPPCertCheck(bool enable)
0352 {
0353     d->internalHostMatch = enable;
0354 }
0355 bool QCATLSHandler::XMPPCertCheck()
0356 {
0357     return d->internalHostMatch;
0358 }
0359 bool QCATLSHandler::certMatchesHostname()
0360 {
0361     if (!d->internalHostMatch) return false;
0362     QCA::CertificateChain peerCert = d->tls->peerCertificateChain();
0363 
0364     if (matchesHostName(peerCert.primary(), d->host))
0365         return true;
0366     
0367     Jid host(d->host);
0368 
0369     foreach( const QString &idOnXmppAddr, peerCert.primary().subjectInfo().values(QCA::XMPP) ) {
0370         if (host.compare(Jid(idOnXmppAddr)))
0371             return true;
0372     }
0373 
0374     return false;
0375 }
0376 
0377 
0378 QCA::TLS *QCATLSHandler::tls() const
0379 {
0380     return d->tls;
0381 }
0382 
0383 int QCATLSHandler::tlsError() const
0384 {
0385     return d->err;
0386 }
0387 
0388 void QCATLSHandler::reset()
0389 {
0390     d->tls->reset();
0391     d->state = 0;
0392 }
0393 
0394 void QCATLSHandler::startClient(const QString &host)
0395 {
0396     d->state = 0;
0397     d->err = -1;
0398     if (d->internalHostMatch) d->host = host;
0399     d->tls->startClient(d->internalHostMatch ? QString() : host);
0400 }
0401 
0402 void QCATLSHandler::write(const QByteArray &a)
0403 {
0404     d->tls->write(a);
0405 }
0406 
0407 void QCATLSHandler::writeIncoming(const QByteArray &a)
0408 {
0409     d->tls->writeIncoming(a);
0410 }
0411 
0412 void QCATLSHandler::continueAfterHandshake()
0413 {
0414     if(d->state == 2) {
0415         d->tls->continueAfterStep();
0416         success();
0417         d->state = 3;
0418     }
0419 }
0420 
0421 void QCATLSHandler::tls_handshaken()
0422 {
0423     d->state = 2;
0424     tlsHandshaken();
0425 }
0426 
0427 void QCATLSHandler::tls_readyRead()
0428 {
0429     readyRead(d->tls->read());
0430 }
0431 
0432 void QCATLSHandler::tls_readyReadOutgoing()
0433 {
0434     int plainBytes;
0435     QByteArray buf = d->tls->readOutgoing(&plainBytes);
0436     readyReadOutgoing(buf, plainBytes);
0437 }
0438 
0439 void QCATLSHandler::tls_closed()
0440 {
0441     closed();
0442 }
0443 
0444 void QCATLSHandler::tls_error()
0445 {
0446     d->err = d->tls->errorCode();
0447     d->state = 0;
0448     fail();
0449 }