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