1 /*
2 * tlshandler.cpp - abstract wrapper for TLS
3 * Copyright (C) 2003 Justin Karneges
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * either version 2
9 of the License, or (at your option) any later version.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 *
20 */
21
22 #include "xmpp.h"
23
24 #include <QTimer>
25 #include "qca.h"
26
27 using namespace XMPP;
28
29 // FIXME: remove this code once qca cert host checking works ...
30 using namespace QCA;
31 #include <QUrl>
32
33 // ip address string to binary (msb), adapted from jdns (adapted from qt)
34 // return: size 4 = ipv4, size 16 = ipv6, size 0 = error
ipaddr_str2bin(const QString & str)35 static QByteArray ipaddr_str2bin(const QString &str)
36 {
37 // ipv6
38 if(str.contains(':'))
39 {
40 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
41 QStringList parts = str.split(':', QString::KeepEmptyParts);
42 #else
43 QStringList parts = str.split(':', Qt::KeepEmptyParts);
44 #endif
45 if(parts.count() < 3 || parts.count() > 8)
46 return QByteArray();
47
48 QByteArray ipv6(16, 0);
49 int at = 16;
50 int fill = 9 - parts.count();
51 for(int n = parts.count() - 1; n >= 0; --n)
52 {
53 if(at <= 0)
54 return QByteArray();
55
56 if(parts[n].isEmpty())
57 {
58 if(n == parts.count() - 1)
59 {
60 if(!parts[n - 1].isEmpty())
61 return QByteArray();
62 ipv6[--at] = 0;
63 ipv6[--at] = 0;
64 }
65 else if(n == 0)
66 {
67 if(!parts[n + 1].isEmpty())
68 return QByteArray();
69 ipv6[--at] = 0;
70 ipv6[--at] = 0;
71 }
72 else
73 {
74 for(int i = 0; i < fill; ++i)
75 {
76 if(at <= 0)
77 return QByteArray();
78 ipv6[--at] = 0;
79 ipv6[--at] = 0;
80 }
81 }
82 }
83 else
84 {
85 if(parts[n].indexOf('.') == -1)
86 {
87 bool ok;
88 int x = parts[n].toInt(&ok, 16);
89 if(!ok || x < 0 || x > 0xffff)
90 return QByteArray();
91 ipv6[--at] = x & 0xff;
92 ipv6[--at] = (x >> 8) & 0xff;
93 }
94 else
95 {
96 if(n != parts.count() - 1)
97 return QByteArray();
98
99 QByteArray buf = ipaddr_str2bin(parts[n]);
100 if(buf.isEmpty())
101 return QByteArray();
102
103 ipv6[--at] = buf[3];
104 ipv6[--at] = buf[2];
105 ipv6[--at] = buf[1];
106 ipv6[--at] = buf[0];
107 --fill;
108 }
109 }
110 }
111
112 return ipv6;
113 }
114 else if(str.contains('.'))
115 {
116 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
117 QStringList parts = str.split('.', QString::KeepEmptyParts);
118 #else
119 QStringList parts = str.split('.', Qt::KeepEmptyParts);
120 #endif
121 if(parts.count() != 4)
122 return QByteArray();
123
124 QByteArray out(4, 0);
125 for(int n = 0; n < 4; ++n)
126 {
127 bool ok;
128 int x = parts[n].toInt(&ok);
129 if(!ok || x < 0 || x > 0xff)
130 return QByteArray();
131 out[n] = (unsigned char)x;
132 }
133 return out;
134 }
135 else
136 return QByteArray();
137 }
138
139 // acedomain must be all lowercase, with no trailing dot or wildcards
cert_match_domain(const QString & certname,const QString & acedomain)140 static bool cert_match_domain(const QString &certname, const QString &acedomain)
141 {
142 // KSSL strips start/end whitespace, even though such whitespace is
143 // probably not legal anyway. (compat)
144 QString name = certname.trimmed();
145
146 // KSSL strips trailing dot, even though the dot is probably not
147 // legal anyway. (compat)
148 if(name.length() > 0 && name[name.length()-1] == '.')
149 name.truncate(name.length()-1);
150
151 // after our compatibility modifications, make sure the name isn't
152 // empty.
153 if(name.isEmpty())
154 return false;
155
156 // lowercase, for later performing case insensitive matching
157 name = name.toLower();
158
159 // ensure the cert field contains valid characters only
160 if(QRegExp("[^a-z0-9\\.\\*\\-]").indexIn(name) >= 0)
161 return false;
162
163 // hack into parts, and require at least 1 part
164 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
165 QStringList parts_name = name.split('.', QString::KeepEmptyParts);
166 #else
167 QStringList parts_name = name.split('.', Qt::KeepEmptyParts);
168 #endif
169 if(parts_name.isEmpty())
170 return false;
171
172 // KSSL checks to make sure the last two parts don't contain
173 // wildcards. I don't know where it is written that this
174 // should be done, but for compat sake we'll do it.
175 if(parts_name[parts_name.count()-1].contains('*'))
176 return false;
177 if(parts_name.count() >= 2 && parts_name[parts_name.count()-2].contains('*'))
178 return false;
179
180 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
181 QStringList parts_compare = acedomain.split('.', QString::KeepEmptyParts);
182 #else
183 QStringList parts_compare = acedomain.split('.', Qt::KeepEmptyParts);
184 #endif
185 if(parts_compare.isEmpty())
186 return false;
187
188 // don't allow empty parts
189 foreach(const QString &s, parts_name)
190 {
191 if(s.isEmpty())
192 return false;
193 }
194 foreach(const QString &s, parts_compare)
195 {
196 if(s.isEmpty())
197 return false;
198 }
199
200 // RFC2818: "Names may contain the wildcard character * which is
201 // considered to match any single domain name component or
202 // component fragment. E.g., *.a.com matches foo.a.com but not
203 // bar.foo.a.com. f*.com matches foo.com but not bar.com."
204 //
205 // This means that for the domain to match it must have the
206 // same number of components, wildcards or not. If there are
207 // wildcards, their scope must only be within the component
208 // they reside in.
209 //
210 // First, make sure the number of parts is equal.
211 if(parts_name.count() != parts_compare.count())
212 return false;
213
214 // Now compare each part
215 for(int n = 0; n < parts_name.count(); ++n)
216 {
217 const QString &p1 = parts_name[n];
218 const QString &p2 = parts_compare[n];
219
220 if(!QRegExp(p1, Qt::CaseSensitive, QRegExp::Wildcard).exactMatch(p2))
221 return false;
222 }
223
224 return true;
225 }
226
227 // ipaddress must be an ipv4 or ipv6 address in binary format
cert_match_ipaddress(const QString & certname,const QByteArray & ipaddress)228 static bool cert_match_ipaddress(const QString &certname, const QByteArray &ipaddress)
229 {
230 // KSSL strips start/end whitespace, even though such whitespace is
231 // probably not legal anyway. (compat)
232 QString name = certname.trimmed();
233
234 // KSSL accepts IPv6 in brackets, which is usually done for URIs, but
235 // IMO sounds very strange for a certificate. We'll follow this
236 // behavior anyway. (compat)
237 if(name.length() >= 2 && name[0] == '[' && name[name.length()-1] == ']')
238 name = name.mid(1, name.length() - 2); // chop off brackets
239
240 // after our compatibility modifications, make sure the name isn't
241 // empty.
242 if(name.isEmpty())
243 return false;
244
245 // convert to binary form
246 QByteArray addr = ipaddr_str2bin(name);
247 if(addr.isEmpty())
248 return false;
249
250 // not the same?
251 if(addr != ipaddress)
252 return false;
253
254 return true;
255 }
256
matchesHostName(const QCA::Certificate & cert,const QString & host)257 static bool matchesHostName(const QCA::Certificate &cert, const QString &host)
258 {
259 QByteArray ipaddr = ipaddr_str2bin(host);
260 if(!ipaddr.isEmpty()) // ip address
261 {
262 // check iPAddress
263 foreach(const QString &s, cert.subjectInfo().values(IPAddress))
264 {
265 if(cert_match_ipaddress(s, ipaddr))
266 return true;
267 }
268
269 // check dNSName
270 foreach(const QString &s, cert.subjectInfo().values(DNS))
271 {
272 if(cert_match_ipaddress(s, ipaddr))
273 return true;
274 }
275
276 // check commonName
277 foreach(const QString &s, cert.subjectInfo().values(CommonName))
278 {
279 if(cert_match_ipaddress(s, ipaddr))
280 return true;
281 }
282 }
283 else // domain
284 {
285 // lowercase
286 QString name = host.toLower();
287
288 // ACE
289 name = QString::fromLatin1(QUrl::toAce(name));
290
291 // don't allow wildcards in the comparison host
292 if(name.contains('*'))
293 return false;
294
295 // strip out trailing dot
296 if(name.length() > 0 && name[name.length()-1] == '.')
297 name.truncate(name.length()-1);
298
299 // make sure the name is not empty after our modifications
300 if(name.isEmpty())
301 return false;
302
303 // check dNSName
304 foreach(const QString &s, cert.subjectInfo().values(DNS))
305 {
306 if(cert_match_domain(s, name))
307 return true;
308 }
309
310 // check commonName
311 foreach(const QString &s, cert.subjectInfo().values(CommonName))
312 {
313 if(cert_match_domain(s, name))
314 return true;
315 }
316 }
317
318 return false;
319 }
320
321 //----------------------------------------------------------------------------
322 // TLSHandler
323 //----------------------------------------------------------------------------
TLSHandler(QObject * parent)324 TLSHandler::TLSHandler(QObject *parent)
325 :QObject(parent)
326 {
327 }
328
~TLSHandler()329 TLSHandler::~TLSHandler()
330 {
331 }
332
333
334 //----------------------------------------------------------------------------
335 // QCATLSHandler
336 //----------------------------------------------------------------------------
337 class QCATLSHandler::Private
338 {
339 public:
340 QCA::TLS *tls;
341 int state, err;
342 QString host;
343 bool internalHostMatch;
344 };
345
QCATLSHandler(QCA::TLS * parent)346 QCATLSHandler::QCATLSHandler(QCA::TLS *parent)
347 :TLSHandler(parent)
348 {
349 d = new Private;
350 d->tls = parent;
351 connect(d->tls, &TLS::handshaken, this, &QCATLSHandler::tls_handshaken);
352 connect(d->tls, &SecureLayer::readyRead, this, &QCATLSHandler::tls_readyRead);
353 connect(d->tls, &SecureLayer::readyReadOutgoing, this, &QCATLSHandler::tls_readyReadOutgoing);
354 connect(d->tls, &SecureLayer::closed, this, &QCATLSHandler::tls_closed);
355 connect(d->tls, &SecureLayer::error, this, &QCATLSHandler::tls_error);
356 d->state = 0;
357 d->err = -1;
358 d->internalHostMatch = false;
359 }
360
~QCATLSHandler()361 QCATLSHandler::~QCATLSHandler()
362 {
363 delete d;
364 }
365
setXMPPCertCheck(bool enable)366 void QCATLSHandler::setXMPPCertCheck(bool enable)
367 {
368 d->internalHostMatch = enable;
369 }
XMPPCertCheck()370 bool QCATLSHandler::XMPPCertCheck()
371 {
372 return d->internalHostMatch;
373 }
certMatchesHostname()374 bool QCATLSHandler::certMatchesHostname()
375 {
376 if (!d->internalHostMatch) return false;
377 QCA::CertificateChain peerCert = d->tls->peerCertificateChain();
378
379 if (matchesHostName(peerCert.primary(), d->host))
380 return true;
381
382 Jid host(d->host);
383
384 foreach( const QString &idOnXmppAddr, peerCert.primary().subjectInfo().values(QCA::XMPP) ) {
385 if (host.compare(Jid(idOnXmppAddr)))
386 return true;
387 }
388
389 return false;
390 }
391
392
tls() const393 QCA::TLS *QCATLSHandler::tls() const
394 {
395 return d->tls;
396 }
397
tlsError() const398 int QCATLSHandler::tlsError() const
399 {
400 return d->err;
401 }
402
reset()403 void QCATLSHandler::reset()
404 {
405 d->tls->reset();
406 d->state = 0;
407 }
408
startClient(const QString & host)409 void QCATLSHandler::startClient(const QString &host)
410 {
411 d->state = 0;
412 d->err = -1;
413 if (d->internalHostMatch) d->host = host;
414 d->tls->startClient(d->internalHostMatch ? QString() : host);
415 }
416
write(const QByteArray & a)417 void QCATLSHandler::write(const QByteArray &a)
418 {
419 d->tls->write(a);
420 }
421
writeIncoming(const QByteArray & a)422 void QCATLSHandler::writeIncoming(const QByteArray &a)
423 {
424 d->tls->writeIncoming(a);
425 }
426
continueAfterHandshake()427 void QCATLSHandler::continueAfterHandshake()
428 {
429 if(d->state == 2) {
430 d->tls->continueAfterStep();
431 success();
432 d->state = 3;
433 }
434 }
435
tls_handshaken()436 void QCATLSHandler::tls_handshaken()
437 {
438 d->state = 2;
439 tlsHandshaken();
440 }
441
tls_readyRead()442 void QCATLSHandler::tls_readyRead()
443 {
444 readyRead(d->tls->read());
445 }
446
tls_readyReadOutgoing()447 void QCATLSHandler::tls_readyReadOutgoing()
448 {
449 int plainBytes;
450 QByteArray buf = d->tls->readOutgoing(&plainBytes);
451 readyReadOutgoing(buf, plainBytes);
452 }
453
tls_closed()454 void QCATLSHandler::tls_closed()
455 {
456 closed();
457 }
458
tls_error()459 void QCATLSHandler::tls_error()
460 {
461 d->err = d->tls->errorCode();
462 d->state = 0;
463 fail();
464 }
465