source: trunk/src/network/kernel/qauthenticator.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 40.2 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtNetwork module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include <qauthenticator.h>
43#include <qauthenticator_p.h>
44#include <qdebug.h>
45#include <qhash.h>
46#include <qbytearray.h>
47#include <qcryptographichash.h>
48#include <qhttp.h>
49#include <qiodevice.h>
50#include <qdatastream.h>
51#include <qendian.h>
52#include <qstring.h>
53#include <qdatetime.h>
54
55//#define NTLMV1_CLIENT
56
57QT_BEGIN_NAMESPACE
58
59#ifdef NTLMV1_CLIENT
60#include "../../3rdparty/des/des.cpp"
61#endif
62
63static QByteArray qNtlmPhase1();
64static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray& phase2data);
65
66/*!
67 \class QAuthenticator
68 \brief The QAuthenticator class provides an authentication object.
69 \since 4.3
70
71 \reentrant
72 \ingroup network
73 \inmodule QtNetwork
74
75 The QAuthenticator class is usually used in the
76 \l{QNetworkAccessManager::}{authenticationRequired()} and
77 \l{QNetworkAccessManager::}{proxyAuthenticationRequired()} signals of QNetworkAccessManager and
78 QAbstractSocket. The class provides a way to pass back the required
79 authentication information to the socket when accessing services that
80 require authentication.
81
82 QAuthenticator supports the following authentication methods:
83 \list
84 \o Basic
85 \o NTLM version 1
86 \o Digest-MD5
87 \endlist
88
89 Note that, in particular, NTLM version 2 is not supported.
90
91 \section1 Options
92
93 In addition to the username and password required for authentication, a
94 QAuthenticator object can also contain additional options. The
95 options() function can be used to query incoming options sent by
96 the server; the setOption() function can
97 be used to set outgoing options, to be processed by the authenticator
98 calculation. The options accepted and provided depend on the authentication
99 type (see method()).
100
101 The following tables list known incoming options as well as accepted
102 outgoing options. The list of incoming options is not exhaustive, since
103 servers may include additional information at any time. The list of
104 outgoing options is exhaustive, however, and no unknown options will be
105 treated or sent back to the server.
106
107 \section2 Basic
108
109 \table
110 \header \o Option \o Direction \o Description
111 \row \o \tt{realm} \o Incoming \o Contains the realm of the authentication, the same as realm()
112 \endtable
113
114 The Basic authentication mechanism supports no outgoing options.
115
116 \section2 NTLM version 1
117
118 The NTLM authentication mechanism currently supports no incoming or outgoing options.
119
120 \section2 Digest-MD5
121
122 \table
123 \header \o Option \o Direction \o Description
124 \row \o \tt{realm} \o Incoming \o Contains the realm of the authentication, the same as realm()
125 \endtable
126
127 The Digest-MD5 authentication mechanism supports no outgoing options.
128
129 \sa QSslSocket
130*/
131
132
133/*!
134 Constructs an empty authentication object
135*/
136QAuthenticator::QAuthenticator()
137 : d(0)
138{
139}
140
141/*!
142 Destructs the object
143*/
144QAuthenticator::~QAuthenticator()
145{
146 if (d && !d->ref.deref())
147 delete d;
148}
149
150/*!
151 Constructs a copy of \a other.
152*/
153QAuthenticator::QAuthenticator(const QAuthenticator &other)
154 : d(other.d)
155{
156 if (d)
157 d->ref.ref();
158}
159
160/*!
161 Assigns the contents of \a other to this authenticator.
162*/
163QAuthenticator &QAuthenticator::operator=(const QAuthenticator &other)
164{
165 if (d == other.d)
166 return *this;
167
168 if (d && !d->ref.deref())
169 delete d;
170
171 d = other.d;
172 if (d)
173 d->ref.ref();
174 return *this;
175}
176
177/*!
178 Returns true if this authenticator is identical to \a other; otherwise
179 returns false.
180*/
181bool QAuthenticator::operator==(const QAuthenticator &other) const
182{
183 if (d == other.d)
184 return true;
185 return d->user == other.d->user
186 && d->password == other.d->password
187 && d->realm == other.d->realm
188 && d->method == other.d->method
189 && d->options == other.d->options;
190}
191
192/*!
193 \fn bool QAuthenticator::operator!=(const QAuthenticator &other) const
194
195 Returns true if this authenticator is different from \a other; otherwise
196 returns false.
197*/
198
199/*!
200 returns the user used for authentication.
201*/
202QString QAuthenticator::user() const
203{
204 return d ? d->user : QString();
205}
206
207/*!
208 Sets the \a user used for authentication.
209*/
210void QAuthenticator::setUser(const QString &user)
211{
212 detach();
213 int separatorPosn = 0;
214
215 switch(d->method) {
216 case QAuthenticatorPrivate::Ntlm:
217 if((separatorPosn = user.indexOf(QLatin1String("\\"))) != -1) {
218 //domain name is present
219 d->realm.clear();
220 d->userDomain = user.left(separatorPosn);
221 d->extractedUser = user.mid(separatorPosn + 1);
222 d->user = user;
223 } else if((separatorPosn = user.indexOf(QLatin1String("@"))) != -1) {
224 //domain name is present
225 d->realm.clear();
226 d->userDomain = user.left(separatorPosn);
227 d->extractedUser = user.left(separatorPosn);
228 d->user = user;
229 } else {
230 d->extractedUser = user;
231 d->user = user;
232 d->realm.clear();
233 d->userDomain.clear();
234 }
235 break;
236 default:
237 d->user = user;
238 d->userDomain.clear();
239 break;
240 }
241}
242
243/*!
244 returns the password used for authentication.
245*/
246QString QAuthenticator::password() const
247{
248 return d ? d->password : QString();
249}
250
251/*!
252 Sets the \a password used for authentication.
253*/
254void QAuthenticator::setPassword(const QString &password)
255{
256 detach();
257 d->password = password;
258}
259
260/*!
261 \internal
262*/
263void QAuthenticator::detach()
264{
265 if (!d) {
266 d = new QAuthenticatorPrivate;
267 d->ref = 1;
268 return;
269 }
270
271 qAtomicDetach(d);
272 d->phase = QAuthenticatorPrivate::Start;
273}
274
275/*!
276 returns the realm requiring authentication.
277*/
278QString QAuthenticator::realm() const
279{
280 return d ? d->realm : QString();
281}
282
283/*!
284 \since 4.7
285 Returns the value related to option \a opt if it was set by the server.
286 See \l{QAuthenticator#Options} for more information on incoming options.
287 If option \a opt isn't found, an invalid QVariant will be returned.
288
289 \sa options(), QAuthenticator#Options
290*/
291QVariant QAuthenticator::option(const QString &opt) const
292{
293 return d ? d->options.value(opt) : QVariant();
294}
295
296/*!
297 \since 4.7
298 Returns all incoming options set in this QAuthenticator object by parsing
299 the server reply. See \l{QAuthenticator#Options} for more information
300 on incoming options.
301
302 \sa option(), QAuthenticator#Options
303*/
304QVariantHash QAuthenticator::options() const
305{
306 return d ? d->options : QVariantHash();
307}
308
309/*!
310 \since 4.7
311
312 Sets the outgoing option \a opt to value \a value.
313 See \l{QAuthenticator#Options} for more information on outgoing options.
314
315 \sa options(), option(), QAuthenticator#Options
316*/
317void QAuthenticator::setOption(const QString &opt, const QVariant &value)
318{
319 detach();
320 d->options.insert(opt, value);
321}
322
323
324/*!
325 Returns true if the authenticator is null.
326*/
327bool QAuthenticator::isNull() const
328{
329 return !d;
330}
331
332QAuthenticatorPrivate::QAuthenticatorPrivate()
333 : ref(0)
334 , method(None)
335 , phase(Start)
336 , nonceCount(0)
337{
338 cnonce = QCryptographicHash::hash(QByteArray::number(qrand(), 16) + QByteArray::number(qrand(), 16),
339 QCryptographicHash::Md5).toHex();
340 nonceCount = 0;
341}
342
343#ifndef QT_NO_HTTP
344void QAuthenticatorPrivate::parseHttpResponse(const QHttpResponseHeader &header, bool isProxy)
345{
346 const QList<QPair<QString, QString> > values = header.values();
347 QList<QPair<QByteArray, QByteArray> > rawValues;
348
349 QList<QPair<QString, QString> >::const_iterator it, end;
350 for (it = values.constBegin(), end = values.constEnd(); it != end; ++it)
351 rawValues.append(qMakePair(it->first.toLatin1(), it->second.toUtf8()));
352
353 // continue in byte array form
354 parseHttpResponse(rawValues, isProxy);
355}
356#endif
357
358void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByteArray> > &values, bool isProxy)
359{
360 const char *search = isProxy ? "proxy-authenticate" : "www-authenticate";
361
362 method = None;
363 /*
364 Fun from the HTTP 1.1 specs, that we currently ignore:
365
366 User agents are advised to take special care in parsing the WWW-
367 Authenticate field value as it might contain more than one challenge,
368 or if more than one WWW-Authenticate header field is provided, the
369 contents of a challenge itself can contain a comma-separated list of
370 authentication parameters.
371 */
372
373 QByteArray headerVal;
374 for (int i = 0; i < values.size(); ++i) {
375 const QPair<QByteArray, QByteArray> &current = values.at(i);
376 if (current.first.toLower() != search)
377 continue;
378 QByteArray str = current.second.toLower();
379 if (method < Basic && str.startsWith("basic")) {
380 method = Basic;
381 headerVal = current.second.mid(6);
382 } else if (method < Ntlm && str.startsWith("ntlm")) {
383 method = Ntlm;
384 headerVal = current.second.mid(5);
385 } else if (method < DigestMd5 && str.startsWith("digest")) {
386 method = DigestMd5;
387 headerVal = current.second.mid(7);
388 }
389 }
390
391 challenge = headerVal.trimmed();
392 QHash<QByteArray, QByteArray> options = parseDigestAuthenticationChallenge(challenge);
393
394 switch(method) {
395 case Basic:
396 if(realm.isEmpty())
397 this->options[QLatin1String("realm")] = realm = QString::fromLatin1(options.value("realm"));
398 if (user.isEmpty())
399 phase = Done;
400 break;
401 case Ntlm:
402 // #### extract from header
403 break;
404 case DigestMd5: {
405 if(realm.isEmpty())
406 this->options[QLatin1String("realm")] = realm = QString::fromLatin1(options.value("realm"));
407 if (options.value("stale").toLower() == "true")
408 phase = Start;
409 if (user.isEmpty())
410 phase = Done;
411 break;
412 }
413 default:
414 realm.clear();
415 challenge = QByteArray();
416 phase = Invalid;
417 }
418}
419
420QByteArray QAuthenticatorPrivate::calculateResponse(const QByteArray &requestMethod, const QByteArray &path)
421{
422 QByteArray response;
423 const char *methodString = 0;
424 switch(method) {
425 case QAuthenticatorPrivate::None:
426 methodString = "";
427 phase = Done;
428 break;
429 case QAuthenticatorPrivate::Plain:
430 response = '\0' + user.toUtf8() + '\0' + password.toUtf8();
431 phase = Done;
432 break;
433 case QAuthenticatorPrivate::Basic:
434 methodString = "Basic ";
435 response = user.toLatin1() + ':' + password.toLatin1();
436 response = response.toBase64();
437 phase = Done;
438 break;
439 case QAuthenticatorPrivate::Login:
440 if (challenge.contains("VXNlciBOYW1lAA==")) {
441 response = user.toUtf8().toBase64();
442 phase = Phase2;
443 } else if (challenge.contains("UGFzc3dvcmQA")) {
444 response = password.toUtf8().toBase64();
445 phase = Done;
446 }
447 break;
448 case QAuthenticatorPrivate::CramMd5:
449 break;
450 case QAuthenticatorPrivate::DigestMd5:
451 methodString = "Digest ";
452 response = digestMd5Response(challenge, requestMethod, path);
453 phase = Done;
454 break;
455 case QAuthenticatorPrivate::Ntlm:
456 methodString = "NTLM ";
457 if (challenge.isEmpty()) {
458 response = qNtlmPhase1().toBase64();
459 if (user.isEmpty())
460 phase = Done;
461 else
462 phase = Phase2;
463 } else {
464 response = qNtlmPhase3(this, QByteArray::fromBase64(challenge)).toBase64();
465 phase = Done;
466 }
467
468 break;
469 }
470 return QByteArray(methodString) + response;
471}
472
473
474// ---------------------------- Digest Md5 code ----------------------------------------
475
476QHash<QByteArray, QByteArray> QAuthenticatorPrivate::parseDigestAuthenticationChallenge(const QByteArray &challenge)
477{
478 QHash<QByteArray, QByteArray> options;
479 // parse the challenge
480 const char *d = challenge.constData();
481 const char *end = d + challenge.length();
482 while (d < end) {
483 while (d < end && (*d == ' ' || *d == '\n' || *d == '\r'))
484 ++d;
485 const char *start = d;
486 while (d < end && *d != '=')
487 ++d;
488 QByteArray key = QByteArray(start, d - start);
489 ++d;
490 if (d >= end)
491 break;
492 bool quote = (*d == '"');
493 if (quote)
494 ++d;
495 if (d >= end)
496 break;
497 start = d;
498 QByteArray value;
499 while (d < end) {
500 bool backslash = false;
501 if (*d == '\\' && d < end - 1) {
502 ++d;
503 backslash = true;
504 }
505 if (!backslash) {
506 if (quote) {
507 if (*d == '"')
508 break;
509 } else {
510 if (*d == ',')
511 break;
512 }
513 }
514 value += *d;
515 ++d;
516 }
517 while (d < end && *d != ',')
518 ++d;
519 ++d;
520 options[key] = value;
521 }
522
523 QByteArray qop = options.value("qop");
524 if (!qop.isEmpty()) {
525 QList<QByteArray> qopoptions = qop.split(',');
526 if (!qopoptions.contains("auth"))
527 return QHash<QByteArray, QByteArray>();
528 // #### can't do auth-int currently
529// if (qop.contains("auth-int"))
530// qop = "auth-int";
531// else if (qop.contains("auth"))
532// qop = "auth";
533// else
534// qop = QByteArray();
535 options["qop"] = "auth";
536 }
537
538 return options;
539}
540
541/*
542 Digest MD5 implementation
543
544 Code taken from RFC 2617
545
546 Currently we don't support the full SASL authentication mechanism (which includes cyphers)
547*/
548
549
550/* calculate request-digest/response-digest as per HTTP Digest spec */
551static QByteArray digestMd5ResponseHelper(
552 const QByteArray &alg,
553 const QByteArray &userName,
554 const QByteArray &realm,
555 const QByteArray &password,
556 const QByteArray &nonce, /* nonce from server */
557 const QByteArray &nonceCount, /* 8 hex digits */
558 const QByteArray &cNonce, /* client nonce */
559 const QByteArray &qop, /* qop-value: "", "auth", "auth-int" */
560 const QByteArray &method, /* method from the request */
561 const QByteArray &digestUri, /* requested URL */
562 const QByteArray &hEntity /* H(entity body) if qop="auth-int" */
563 )
564{
565 QCryptographicHash hash(QCryptographicHash::Md5);
566 hash.addData(userName);
567 hash.addData(":", 1);
568 hash.addData(realm);
569 hash.addData(":", 1);
570 hash.addData(password);
571 QByteArray ha1 = hash.result();
572 if (alg.toLower() == "md5-sess") {
573 hash.reset();
574 // RFC 2617 contains an error, it was:
575 // hash.addData(ha1);
576 // but according to the errata page at http://www.rfc-editor.org/errata_list.php, ID 1649, it
577 // must be the following line:
578 hash.addData(ha1.toHex());
579 hash.addData(":", 1);
580 hash.addData(nonce);
581 hash.addData(":", 1);
582 hash.addData(cNonce);
583 ha1 = hash.result();
584 };
585 ha1 = ha1.toHex();
586
587 // calculate H(A2)
588 hash.reset();
589 hash.addData(method);
590 hash.addData(":", 1);
591 hash.addData(digestUri);
592 if (qop.toLower() == "auth-int") {
593 hash.addData(":", 1);
594 hash.addData(hEntity);
595 }
596 QByteArray ha2hex = hash.result().toHex();
597
598 // calculate response
599 hash.reset();
600 hash.addData(ha1);
601 hash.addData(":", 1);
602 hash.addData(nonce);
603 hash.addData(":", 1);
604 if (!qop.isNull()) {
605 hash.addData(nonceCount);
606 hash.addData(":", 1);
607 hash.addData(cNonce);
608 hash.addData(":", 1);
609 hash.addData(qop);
610 hash.addData(":", 1);
611 }
612 hash.addData(ha2hex);
613 return hash.result().toHex();
614}
615
616QByteArray QAuthenticatorPrivate::digestMd5Response(const QByteArray &challenge, const QByteArray &method, const QByteArray &path)
617{
618 QHash<QByteArray,QByteArray> options = parseDigestAuthenticationChallenge(challenge);
619
620 ++nonceCount;
621 QByteArray nonceCountString = QByteArray::number(nonceCount, 16);
622 while (nonceCountString.length() < 8)
623 nonceCountString.prepend('0');
624
625 QByteArray nonce = options.value("nonce");
626 QByteArray opaque = options.value("opaque");
627 QByteArray qop = options.value("qop");
628
629// qDebug() << "calculating digest: method=" << method << "path=" << path;
630 QByteArray response = digestMd5ResponseHelper(options.value("algorithm"), user.toLatin1(),
631 realm.toLatin1(), password.toLatin1(),
632 nonce, nonceCountString,
633 cnonce, qop, method,
634 path, QByteArray());
635
636
637 QByteArray credentials;
638 credentials += "username=\"" + user.toLatin1() + "\", ";
639 credentials += "realm=\"" + realm.toLatin1() + "\", ";
640 credentials += "nonce=\"" + nonce + "\", ";
641 credentials += "uri=\"" + path + "\", ";
642 if (!opaque.isEmpty())
643 credentials += "opaque=\"" + opaque + "\", ";
644 credentials += "response=\"" + response + '\"';
645 if (!options.value("algorithm").isEmpty())
646 credentials += ", algorithm=" + options.value("algorithm");
647 if (!options.value("qop").isEmpty()) {
648 credentials += ", qop=" + qop + ", ";
649 credentials += "nc=" + nonceCountString + ", ";
650 credentials += "cnonce=\"" + cnonce + '\"';
651 }
652
653 return credentials;
654}
655
656// ---------------------------- Digest Md5 code ----------------------------------------
657
658
659
660/*
661 * NTLM message flags.
662 *
663 * Copyright (c) 2004 Andrey Panin <[email protected]>
664 *
665 * This software is released under the MIT license.
666 */
667
668/*
669 * Indicates that Unicode strings are supported for use in security
670 * buffer data.
671 */
672#define NTLMSSP_NEGOTIATE_UNICODE 0x00000001
673
674/*
675 * Indicates that OEM strings are supported for use in security buffer data.
676 */
677#define NTLMSSP_NEGOTIATE_OEM 0x00000002
678
679/*
680 * Requests that the server's authentication realm be included in the
681 * Type 2 message.
682 */
683#define NTLMSSP_REQUEST_TARGET 0x00000004
684
685/*
686 * Specifies that authenticated communication between the client and server
687 * should carry a digital signature (message integrity).
688 */
689#define NTLMSSP_NEGOTIATE_SIGN 0x00000010
690
691/*
692 * Specifies that authenticated communication between the client and server
693 * should be encrypted (message confidentiality).
694 */
695#define NTLMSSP_NEGOTIATE_SEAL 0x00000020
696
697/*
698 * Indicates that datagram authentication is being used.
699 */
700#define NTLMSSP_NEGOTIATE_DATAGRAM 0x00000040
701
702/*
703 * Indicates that the LAN Manager session key should be
704 * used for signing and sealing authenticated communications.
705 */
706#define NTLMSSP_NEGOTIATE_LM_KEY 0x00000080
707
708/*
709 * Indicates that NTLM authentication is being used.
710 */
711#define NTLMSSP_NEGOTIATE_NTLM 0x00000200
712
713/*
714 * Sent by the client in the Type 1 message to indicate that the name of the
715 * domain in which the client workstation has membership is included in the
716 * message. This is used by the server to determine whether the client is
717 * eligible for local authentication.
718 */
719#define NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED 0x00001000
720
721/*
722 * Sent by the client in the Type 1 message to indicate that the client
723 * workstation's name is included in the message. This is used by the server
724 * to determine whether the client is eligible for local authentication.
725 */
726#define NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED 0x00002000
727
728/*
729 * Sent by the server to indicate that the server and client are on the same
730 * machine. Implies that the client may use the established local credentials
731 * for authentication instead of calculating a response to the challenge.
732 */
733#define NTLMSSP_NEGOTIATE_LOCAL_CALL 0x00004000
734
735/*
736 * Indicates that authenticated communication between the client and server
737 * should be signed with a "dummy" signature.
738 */
739#define NTLMSSP_NEGOTIATE_ALWAYS_SIGN 0x00008000
740
741/*
742 * Sent by the server in the Type 2 message to indicate that the target
743 * authentication realm is a domain.
744 */
745#define NTLMSSP_TARGET_TYPE_DOMAIN 0x00010000
746
747/*
748 * Sent by the server in the Type 2 message to indicate that the target
749 * authentication realm is a server.
750 */
751#define NTLMSSP_TARGET_TYPE_SERVER 0x00020000
752
753/*
754 * Sent by the server in the Type 2 message to indicate that the target
755 * authentication realm is a share. Presumably, this is for share-level
756 * authentication. Usage is unclear.
757 */
758#define NTLMSSP_TARGET_TYPE_SHARE 0x00040000
759
760/*
761 * Indicates that the NTLM2 signing and sealing scheme should be used for
762 * protecting authenticated communications. Note that this refers to a
763 * particular session security scheme, and is not related to the use of
764 * NTLMv2 authentication.
765 */
766#define NTLMSSP_NEGOTIATE_NTLM2 0x00080000
767
768/*
769 * Sent by the server in the Type 2 message to indicate that it is including
770 * a Target Information block in the message. The Target Information block
771 * is used in the calculation of the NTLMv2 response.
772 */
773#define NTLMSSP_NEGOTIATE_TARGET_INFO 0x00800000
774
775/*
776 * Indicates that 128-bit encryption is supported.
777 */
778#define NTLMSSP_NEGOTIATE_128 0x20000000
779
780/*
781 * Indicates that the client will provide an encrypted master session key in
782 * the "Session Key" field of the Type 3 message. This is used in signing and
783 * sealing, and is RC4-encrypted using the previous session key as the
784 * encryption key.
785 */
786#define NTLMSSP_NEGOTIATE_KEY_EXCHANGE 0x40000000
787
788/*
789 * Indicates that 56-bit encryption is supported.
790 */
791#define NTLMSSP_NEGOTIATE_56 0x80000000
792
793/*
794 * AvId values
795 */
796#define AVTIMESTAMP 7
797
798//#define NTLMV1_CLIENT
799
800
801//************************Global variables***************************
802
803const int blockSize = 64; //As per RFC2104 Block-size is 512 bits
804const int nDigestLen = 16; //Trunctaion Length of the Hmac-Md5 digest
805const quint8 respversion = 1;
806const quint8 hirespversion = 1;
807
808/* usage:
809 // fill up ctx with what we know.
810 QByteArray response = qNtlmPhase1(ctx);
811 // send response (b64 encoded??)
812 // get response from server (b64 decode?)
813 Phase2Block pb;
814 qNtlmDecodePhase2(response, pb);
815 response = qNtlmPhase3(ctx, pb);
816 // send response (b64 encoded??)
817*/
818
819/*
820 TODO:
821 - Fix unicode handling
822 - add v2 handling
823*/
824
825class QNtlmBuffer {
826public:
827 QNtlmBuffer() : len(0), maxLen(0), offset(0) {}
828 quint16 len;
829 quint16 maxLen;
830 quint32 offset;
831 enum { Size = 8 };
832};
833
834class QNtlmPhase1BlockBase
835{
836public:
837 char magic[8];
838 quint32 type;
839 quint32 flags;
840 QNtlmBuffer domain;
841 QNtlmBuffer workstation;
842 enum { Size = 32 };
843};
844
845// ################# check paddings
846class QNtlmPhase2BlockBase
847{
848public:
849 char magic[8];
850 quint32 type;
851 QNtlmBuffer targetName;
852 quint32 flags;
853 unsigned char challenge[8];
854 quint32 context[2];
855 QNtlmBuffer targetInfo;
856 enum { Size = 48 };
857};
858
859class QNtlmPhase3BlockBase {
860public:
861 char magic[8];
862 quint32 type;
863 QNtlmBuffer lmResponse;
864 QNtlmBuffer ntlmResponse;
865 QNtlmBuffer domain;
866 QNtlmBuffer user;
867 QNtlmBuffer workstation;
868 QNtlmBuffer sessionKey;
869 quint32 flags;
870 enum { Size = 64 };
871};
872
873static void qStreamNtlmBuffer(QDataStream& ds, const QByteArray& s)
874{
875 ds.writeRawData(s.constData(), s.size());
876}
877
878
879static void qStreamNtlmString(QDataStream& ds, const QString& s, bool unicode)
880{
881 if (!unicode) {
882 qStreamNtlmBuffer(ds, s.toLatin1());
883 return;
884 }
885 const ushort *d = s.utf16();
886 for (int i = 0; i < s.length(); ++i)
887 ds << d[i];
888}
889
890
891
892static int qEncodeNtlmBuffer(QNtlmBuffer& buf, int offset, const QByteArray& s)
893{
894 buf.len = s.size();
895 buf.maxLen = buf.len;
896 buf.offset = (offset + 1) & ~1;
897 return buf.offset + buf.len;
898}
899
900
901static int qEncodeNtlmString(QNtlmBuffer& buf, int offset, const QString& s, bool unicode)
902{
903 if (!unicode)
904 return qEncodeNtlmBuffer(buf, offset, s.toLatin1());
905 buf.len = 2 * s.length();
906 buf.maxLen = buf.len;
907 buf.offset = (offset + 1) & ~1;
908 return buf.offset + buf.len;
909}
910
911
912static QDataStream& operator<<(QDataStream& s, const QNtlmBuffer& b)
913{
914 s << b.len << b.maxLen << b.offset;
915 return s;
916}
917
918static QDataStream& operator>>(QDataStream& s, QNtlmBuffer& b)
919{
920 s >> b.len >> b.maxLen >> b.offset;
921 return s;
922}
923
924
925class QNtlmPhase1Block : public QNtlmPhase1BlockBase
926{ // request
927public:
928 QNtlmPhase1Block() {
929 qstrncpy(magic, "NTLMSSP", 8);
930 type = 1;
931 flags = NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_REQUEST_TARGET;
932 }
933
934 // extracted
935 QString domainStr, workstationStr;
936};
937
938
939class QNtlmPhase2Block : public QNtlmPhase2BlockBase
940{ // challenge
941public:
942 QNtlmPhase2Block() {
943 magic[0] = 0;
944 type = 0xffffffff;
945 }
946
947 // extracted
948 QString targetNameStr, targetInfoStr;
949 QByteArray targetInfoBuff;
950};
951
952
953
954class QNtlmPhase3Block : public QNtlmPhase3BlockBase { // response
955public:
956 QNtlmPhase3Block() {
957 qstrncpy(magic, "NTLMSSP", 8);
958 type = 3;
959 flags = NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_NEGOTIATE_TARGET_INFO;
960 }
961
962 // extracted
963 QByteArray lmResponseBuf, ntlmResponseBuf;
964 QString domainStr, userStr, workstationStr, sessionKeyStr;
965 QByteArray v2Hash;
966};
967
968
969static QDataStream& operator<<(QDataStream& s, const QNtlmPhase1Block& b) {
970 bool unicode = (b.flags & NTLMSSP_NEGOTIATE_UNICODE);
971
972 s.writeRawData(b.magic, sizeof(b.magic));
973 s << b.type;
974 s << b.flags;
975 s << b.domain;
976 s << b.workstation;
977 if (!b.domainStr.isEmpty())
978 qStreamNtlmString(s, b.domainStr, unicode);
979 if (!b.workstationStr.isEmpty())
980 qStreamNtlmString(s, b.workstationStr, unicode);
981 return s;
982}
983
984
985static QDataStream& operator<<(QDataStream& s, const QNtlmPhase3Block& b) {
986 bool unicode = (b.flags & NTLMSSP_NEGOTIATE_UNICODE);
987 s.writeRawData(b.magic, sizeof(b.magic));
988 s << b.type;
989 s << b.lmResponse;
990 s << b.ntlmResponse;
991 s << b.domain;
992 s << b.user;
993 s << b.workstation;
994 s << b.sessionKey;
995 s << b.flags;
996
997 if (!b.domainStr.isEmpty())
998 qStreamNtlmString(s, b.domainStr, unicode);
999
1000 qStreamNtlmString(s, b.userStr, unicode);
1001
1002 if (!b.workstationStr.isEmpty())
1003 qStreamNtlmString(s, b.workstationStr, unicode);
1004
1005 // Send auth info
1006 qStreamNtlmBuffer(s, b.lmResponseBuf);
1007 qStreamNtlmBuffer(s, b.ntlmResponseBuf);
1008
1009
1010 return s;
1011}
1012
1013
1014static QByteArray qNtlmPhase1()
1015{
1016 QByteArray rc;
1017 QDataStream ds(&rc, QIODevice::WriteOnly);
1018 ds.setByteOrder(QDataStream::LittleEndian);
1019 QNtlmPhase1Block pb;
1020 ds << pb;
1021 return rc;
1022}
1023
1024
1025static QByteArray qStringAsUcs2Le(const QString& src)
1026{
1027 QByteArray rc(2*src.length(), 0);
1028 const unsigned short *s = src.utf16();
1029 unsigned short *d = (unsigned short*)rc.data();
1030 for (int i = 0; i < src.length(); ++i) {
1031 d[i] = qToLittleEndian(s[i]);
1032 }
1033 return rc;
1034}
1035
1036
1037static QString qStringFromUcs2Le(const QByteArray& src)
1038{
1039 Q_ASSERT(src.size() % 2 == 0);
1040 unsigned short *d = (unsigned short*)src.data();
1041 for (int i = 0; i < src.length() / 2; ++i) {
1042 d[i] = qFromLittleEndian(d[i]);
1043 }
1044 return QString((const QChar *)src.data(), src.size()/2);
1045}
1046
1047#ifdef NTLMV1_CLIENT
1048static QByteArray qEncodeNtlmResponse(const QAuthenticatorPrivate *ctx, const QNtlmPhase2Block& ch)
1049{
1050 QCryptographicHash md4(QCryptographicHash::Md4);
1051 QByteArray asUcs2Le = qStringAsUcs2Le(ctx->password);
1052 md4.addData(asUcs2Le.data(), asUcs2Le.size());
1053
1054 unsigned char md4hash[22];
1055 memset(md4hash, 0, sizeof(md4hash));
1056 QByteArray hash = md4.result();
1057 Q_ASSERT(hash.size() == 16);
1058 memcpy(md4hash, hash.constData(), 16);
1059
1060 QByteArray rc(24, 0);
1061 deshash((unsigned char *)rc.data(), md4hash, (unsigned char *)ch.challenge);
1062 deshash((unsigned char *)rc.data() + 8, md4hash + 7, (unsigned char *)ch.challenge);
1063 deshash((unsigned char *)rc.data() + 16, md4hash + 14, (unsigned char *)ch.challenge);
1064
1065 hash.fill(0);
1066 return rc;
1067}
1068
1069
1070static QByteArray qEncodeLmResponse(const QAuthenticatorPrivate *ctx, const QNtlmPhase2Block& ch)
1071{
1072 QByteArray hash(21, 0);
1073 QByteArray key(14, 0);
1074 qstrncpy(key.data(), ctx->password.toUpper().toLatin1(), 14);
1075 const char *block = "KGS!@#$%";
1076
1077 deshash((unsigned char *)hash.data(), (unsigned char *)key.data(), (unsigned char *)block);
1078 deshash((unsigned char *)hash.data() + 8, (unsigned char *)key.data() + 7, (unsigned char *)block);
1079 key.fill(0);
1080
1081 QByteArray rc(24, 0);
1082 deshash((unsigned char *)rc.data(), (unsigned char *)hash.data(), ch.challenge);
1083 deshash((unsigned char *)rc.data() + 8, (unsigned char *)hash.data() + 7, ch.challenge);
1084 deshash((unsigned char *)rc.data() + 16, (unsigned char *)hash.data() + 14, ch.challenge);
1085
1086 hash.fill(0);
1087 return rc;
1088}
1089#endif
1090
1091/*********************************************************************
1092* Function Name: qEncodeHmacMd5
1093* Params:
1094* key: Type - QByteArray
1095* - It is the Authentication key
1096* message: Type - QByteArray
1097* - This is the actual message which will be encoded
1098* using HMacMd5 hash algorithm
1099*
1100* Return Value:
1101* hmacDigest: Type - QByteArray
1102*
1103* Description:
1104* This function will be used to encode the input message using
1105* HMacMd5 hash algorithm.
1106*
1107* As per the RFC2104 the HMacMd5 algorithm can be specified
1108* ---------------------------------------
1109* MD5(K XOR opad, MD5(K XOR ipad, text))
1110* ---------------------------------------
1111*
1112*********************************************************************/
1113QByteArray qEncodeHmacMd5(QByteArray &key, const QByteArray &message)
1114{
1115 Q_ASSERT_X(!(message.isEmpty()),"qEncodeHmacMd5", "Empty message check");
1116 Q_ASSERT_X(!(key.isEmpty()),"qEncodeHmacMd5", "Empty key check");
1117
1118 QCryptographicHash hash(QCryptographicHash::Md5);
1119 QByteArray hMsg;
1120
1121 QByteArray iKeyPad(blockSize, 0x36);
1122 QByteArray oKeyPad(blockSize, 0x5c);
1123
1124 hash.reset();
1125 // Adjust the key length to blockSize
1126
1127 if(blockSize < key.length()) {
1128 hash.addData(key);
1129 key = hash.result(); //MD5 will always return 16 bytes length output
1130 }
1131
1132 //Key will be <= 16 or 20 bytes as hash function (MD5 or SHA hash algorithms)
1133 //key size can be max of Block size only
1134 key = key.leftJustified(blockSize,0,true);
1135
1136 //iKeyPad, oKeyPad and key are all of same size "blockSize"
1137
1138 //xor of iKeyPad with Key and store the result into iKeyPad
1139 for(int i = 0; i<key.size();i++) {
1140 iKeyPad[i] = key[i]^iKeyPad[i];
1141 }
1142
1143 //xor of oKeyPad with Key and store the result into oKeyPad
1144 for(int i = 0; i<key.size();i++) {
1145 oKeyPad[i] = key[i]^oKeyPad[i];
1146 }
1147
1148 iKeyPad.append(message); // (K0 xor ipad) || text
1149
1150 hash.reset();
1151 hash.addData(iKeyPad);
1152 hMsg = hash.result();
1153 //Digest gen after pass-1: H((K0 xor ipad)||text)
1154
1155 QByteArray hmacDigest;
1156 oKeyPad.append(hMsg);
1157 hash.reset();
1158 hash.addData(oKeyPad);
1159 hmacDigest = hash.result();
1160 // H((K0 xor opad )|| H((K0 xor ipad) || text))
1161
1162 /*hmacDigest should not be less than half the length of the HMAC output
1163 (to match the birthday attack bound) and not less than 80 bits
1164 (a suitable lower bound on the number of bits that need to be
1165 predicted by an attacker).
1166 Refer RFC 2104 for more details on truncation part */
1167
1168 /*MD5 hash always returns 16 byte digest only and HMAC-MD5 spec
1169 (RFC 2104) also says digest length should be 16 bytes*/
1170 return hmacDigest;
1171}
1172
1173static QByteArray qCreatev2Hash(const QAuthenticatorPrivate *ctx,
1174 QNtlmPhase3Block *phase3)
1175{
1176 Q_ASSERT(phase3 != 0);
1177 // since v2 Hash is need for both NTLMv2 and LMv2 it is calculated
1178 // only once and stored and reused
1179 if(phase3->v2Hash.size() == 0) {
1180 QCryptographicHash md4(QCryptographicHash::Md4);
1181 QByteArray passUnicode = qStringAsUcs2Le(ctx->password);
1182 md4.addData(passUnicode.data(), passUnicode.size());
1183
1184 QByteArray hashKey = md4.result();
1185 Q_ASSERT(hashKey.size() == 16);
1186 // Assuming the user and domain is always unicode in challenge
1187 QByteArray message =
1188 qStringAsUcs2Le(ctx->extractedUser.toUpper()) +
1189 qStringAsUcs2Le(phase3->domainStr);
1190
1191 phase3->v2Hash = qEncodeHmacMd5(hashKey, message);
1192 }
1193 return phase3->v2Hash;
1194}
1195
1196static QByteArray clientChallenge(const QAuthenticatorPrivate *ctx)
1197{
1198 Q_ASSERT(ctx->cnonce.size() >= 8);
1199 QByteArray clientCh = ctx->cnonce.right(8);
1200 return clientCh;
1201}
1202
1203// caller has to ensure a valid targetInfoBuff
1204static QByteArray qExtractServerTime(const QByteArray& targetInfoBuff)
1205{
1206 QByteArray timeArray;
1207 QDataStream ds(targetInfoBuff);
1208 ds.setByteOrder(QDataStream::LittleEndian);
1209
1210 quint16 avId;
1211 quint16 avLen;
1212
1213 ds >> avId;
1214 ds >> avLen;
1215 while(avId != 0) {
1216 if(avId == AVTIMESTAMP) {
1217 timeArray.resize(avLen);
1218 //avLen size of QByteArray is allocated
1219 ds.readRawData(timeArray.data(), avLen);
1220 break;
1221 }
1222 ds.skipRawData(avLen);
1223 ds >> avId;
1224 ds >> avLen;
1225 }
1226 return timeArray;
1227}
1228
1229static QByteArray qEncodeNtlmv2Response(const QAuthenticatorPrivate *ctx,
1230 const QNtlmPhase2Block& ch,
1231 QNtlmPhase3Block *phase3)
1232{
1233 Q_ASSERT(phase3 != 0);
1234 // return value stored in phase3
1235 qCreatev2Hash(ctx, phase3);
1236
1237 QByteArray temp;
1238 QDataStream ds(&temp, QIODevice::WriteOnly);
1239 ds.setByteOrder(QDataStream::LittleEndian);
1240
1241 ds << respversion;
1242 ds << hirespversion;
1243
1244 //Reserved
1245 QByteArray reserved1(6, 0);
1246 ds.writeRawData(reserved1.constData(), reserved1.size());
1247
1248 quint64 time = 0;
1249 QByteArray timeArray;
1250
1251 if(ch.targetInfo.len)
1252 {
1253 timeArray = qExtractServerTime(ch.targetInfoBuff);
1254 }
1255
1256 //if server sends time, use it instead of current time
1257 if(timeArray.size()) {
1258 ds.writeRawData(timeArray.constData(), timeArray.size());
1259 } else {
1260 QDateTime currentTime(QDate::currentDate(),
1261 QTime::currentTime(), Qt::UTC);
1262
1263 // number of seconds between 1601 and epoc(1970)
1264 // 369 years, 89 leap years
1265 // ((369 * 365) + 89) * 24 * 3600 = 11644473600
1266
1267 time = Q_UINT64_C(currentTime.toTime_t() + 11644473600);
1268
1269 // represented as 100 nano seconds
1270 time = Q_UINT64_C(time * 10000000);
1271 ds << time;
1272 }
1273
1274 //8 byte client challenge
1275 QByteArray clientCh = clientChallenge(ctx);
1276 ds.writeRawData(clientCh.constData(), clientCh.size());
1277
1278 //Reserved
1279 QByteArray reserved2(4, 0);
1280 ds.writeRawData(reserved2.constData(), reserved2.size());
1281
1282 if (ch.targetInfo.len > 0) {
1283 ds.writeRawData(ch.targetInfoBuff.constData(),
1284 ch.targetInfoBuff.size());
1285 }
1286
1287 //Reserved
1288 QByteArray reserved3(4, 0);
1289 ds.writeRawData(reserved3.constData(), reserved3.size());
1290
1291 QByteArray message((const char*)ch.challenge, sizeof(ch.challenge));
1292 message.append(temp);
1293
1294 QByteArray ntChallengeResp = qEncodeHmacMd5(phase3->v2Hash, message);
1295 ntChallengeResp.append(temp);
1296
1297 return ntChallengeResp;
1298}
1299
1300static QByteArray qEncodeLmv2Response(const QAuthenticatorPrivate *ctx,
1301 const QNtlmPhase2Block& ch,
1302 QNtlmPhase3Block *phase3)
1303{
1304 Q_ASSERT(phase3 != 0);
1305 // return value stored in phase3
1306 qCreatev2Hash(ctx, phase3);
1307
1308 QByteArray message((const char*)ch.challenge, sizeof(ch.challenge));
1309 QByteArray clientCh = clientChallenge(ctx);
1310
1311 message.append(clientCh);
1312
1313 QByteArray lmChallengeResp = qEncodeHmacMd5(phase3->v2Hash, message);
1314 lmChallengeResp.append(clientCh);
1315
1316 return lmChallengeResp;
1317}
1318
1319static bool qNtlmDecodePhase2(const QByteArray& data, QNtlmPhase2Block& ch)
1320{
1321 Q_ASSERT(QNtlmPhase2BlockBase::Size == sizeof(QNtlmPhase2BlockBase));
1322 if (data.size() < QNtlmPhase2BlockBase::Size)
1323 return false;
1324
1325
1326 QDataStream ds(data);
1327 ds.setByteOrder(QDataStream::LittleEndian);
1328 if (ds.readRawData(ch.magic, 8) < 8)
1329 return false;
1330 if (strncmp(ch.magic, "NTLMSSP", 8) != 0)
1331 return false;
1332
1333 ds >> ch.type;
1334 if (ch.type != 2)
1335 return false;
1336
1337 ds >> ch.targetName;
1338 ds >> ch.flags;
1339 if (ds.readRawData((char *)ch.challenge, 8) < 8)
1340 return false;
1341 ds >> ch.context[0] >> ch.context[1];
1342 ds >> ch.targetInfo;
1343
1344 if (ch.targetName.len > 0) {
1345 if (ch.targetName.len + ch.targetName.offset >= (unsigned)data.size())
1346 return false;
1347
1348 ch.targetNameStr = qStringFromUcs2Le(data.mid(ch.targetName.offset, ch.targetName.len));
1349 }
1350
1351 if (ch.targetInfo.len > 0) {
1352 if (ch.targetInfo.len + ch.targetInfo.offset > (unsigned)data.size())
1353 return false;
1354
1355 ch.targetInfoBuff = data.mid(ch.targetInfo.offset, ch.targetInfo.len);
1356 }
1357
1358 return true;
1359}
1360
1361
1362static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray& phase2data)
1363{
1364 QNtlmPhase2Block ch;
1365 if (!qNtlmDecodePhase2(phase2data, ch))
1366 return QByteArray();
1367
1368 QByteArray rc;
1369 QDataStream ds(&rc, QIODevice::WriteOnly);
1370 ds.setByteOrder(QDataStream::LittleEndian);
1371 QNtlmPhase3Block pb;
1372
1373 bool unicode = ch.flags & NTLMSSP_NEGOTIATE_UNICODE;
1374
1375 pb.flags = NTLMSSP_NEGOTIATE_NTLM;
1376 if (unicode)
1377 pb.flags |= NTLMSSP_NEGOTIATE_UNICODE;
1378 else
1379 pb.flags |= NTLMSSP_NEGOTIATE_OEM;
1380
1381
1382 int offset = QNtlmPhase3BlockBase::Size;
1383 Q_ASSERT(QNtlmPhase3BlockBase::Size == sizeof(QNtlmPhase3BlockBase));
1384
1385 if(ctx->userDomain.isEmpty()) {
1386 offset = qEncodeNtlmString(pb.domain, offset, ch.targetNameStr, unicode);
1387 pb.domainStr = ch.targetNameStr;
1388 } else {
1389 offset = qEncodeNtlmString(pb.domain, offset, ctx->userDomain, unicode);
1390 pb.domainStr = ctx->userDomain;
1391 }
1392
1393 offset = qEncodeNtlmString(pb.user, offset, ctx->extractedUser, unicode);
1394 pb.userStr = ctx->extractedUser;
1395
1396 offset = qEncodeNtlmString(pb.workstation, offset, ctx->workstation, unicode);
1397 pb.workstationStr = ctx->workstation;
1398
1399 // Get LM response
1400#ifdef NTLMV1_CLIENT
1401 pb.lmResponseBuf = qEncodeLmResponse(ctx, ch);
1402#else
1403 if (ch.targetInfo.len > 0) {
1404 pb.lmResponseBuf = QByteArray();
1405 } else {
1406 pb.lmResponseBuf = qEncodeLmv2Response(ctx, ch, &pb);
1407 }
1408#endif
1409 offset = qEncodeNtlmBuffer(pb.lmResponse, offset, pb.lmResponseBuf);
1410
1411 // Get NTLM response
1412#ifdef NTLMV1_CLIENT
1413 pb.ntlmResponseBuf = qEncodeNtlmResponse(ctx, ch);
1414#else
1415 pb.ntlmResponseBuf = qEncodeNtlmv2Response(ctx, ch, &pb);
1416#endif
1417 offset = qEncodeNtlmBuffer(pb.ntlmResponse, offset, pb.ntlmResponseBuf);
1418
1419
1420 // Encode and send
1421 ds << pb;
1422
1423 return rc;
1424}
1425
1426
1427
1428QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.