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 }