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

Last change on this file since 761 was 651, checked in by Dmitry A. Kuminov, 16 years ago

trunk: Merged in qt 4.6.2 sources.

File size: 28.2 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2010 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
54QT_BEGIN_NAMESPACE
55
56#include "../../3rdparty/des/des.cpp"
57
58static QByteArray qNtlmPhase1();
59static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray& phase2data);
60
61/*!
62 \class QAuthenticator
63 \brief The QAuthenticator class provides an authentication object.
64 \since 4.3
65
66 \reentrant
67 \ingroup network
68 \inmodule QtNetwork
69
70 The QAuthenticator class is usually used in the
71 \l{QNetworkAccessManager::}{authenticationRequired()} and
72 \l{QNetworkAccessManager::}{proxyAuthenticationRequired()} signals of QNetworkAccessManager and
73 QAbstractSocket. The class provides a way to pass back the required
74 authentication information to the socket when accessing services that
75 require authentication.
76
77 QAuthenticator supports the following authentication methods:
78 \list
79 \o Basic
80 \o NTLM version 1
81 \o Digest-MD5
82 \endlist
83
84 Note that, in particular, NTLM version 2 is not supported.
85
86 \sa QSslSocket
87*/
88
89
90/*!
91 Constructs an empty authentication object
92*/
93QAuthenticator::QAuthenticator()
94 : d(0)
95{
96}
97
98/*!
99 Destructs the object
100*/
101QAuthenticator::~QAuthenticator()
102{
103 if (d && !d->ref.deref())
104 delete d;
105}
106
107/*!
108 Constructs a copy of \a other.
109*/
110QAuthenticator::QAuthenticator(const QAuthenticator &other)
111 : d(other.d)
112{
113 if (d)
114 d->ref.ref();
115}
116
117/*!
118 Assigns the contents of \a other to this authenticator.
119*/
120QAuthenticator &QAuthenticator::operator=(const QAuthenticator &other)
121{
122 if (d == other.d)
123 return *this;
124 detach();
125 d->user = other.d->user;
126 d->password = other.d->password;
127 return *this;
128}
129
130/*!
131 Returns true if this authenticator is identical to \a other; otherwise
132 returns false.
133*/
134bool QAuthenticator::operator==(const QAuthenticator &other) const
135{
136 if (d == other.d)
137 return true;
138 return d->user == other.d->user
139 && d->password == other.d->password
140 && d->realm == other.d->realm
141 && d->method == other.d->method;
142}
143
144/*!
145 \fn bool QAuthenticator::operator!=(const QAuthenticator &other) const
146
147 Returns true if this authenticator is different from \a other; otherwise
148 returns false.
149*/
150
151/*!
152 returns the user used for authentication.
153*/
154QString QAuthenticator::user() const
155{
156 return d ? d->user : QString();
157}
158
159/*!
160 Sets the \a user used for authentication.
161*/
162void QAuthenticator::setUser(const QString &user)
163{
164 detach();
165 d->user = user;
166}
167
168/*!
169 returns the password used for authentication.
170*/
171QString QAuthenticator::password() const
172{
173 return d ? d->password : QString();
174}
175
176/*!
177 Sets the \a password used for authentication.
178*/
179void QAuthenticator::setPassword(const QString &password)
180{
181 detach();
182 d->password = password;
183}
184
185/*!
186 \internal
187*/
188void QAuthenticator::detach()
189{
190 if (!d) {
191 d = new QAuthenticatorPrivate;
192 d->ref = 1;
193 return;
194 }
195
196 qAtomicDetach(d);
197 d->phase = QAuthenticatorPrivate::Start;
198}
199
200/*!
201 returns the realm requiring authentication.
202*/
203QString QAuthenticator::realm() const
204{
205 return d ? d->realm : QString();
206}
207
208
209/*!
210 returns true if the authenticator is null.
211*/
212bool QAuthenticator::isNull() const
213{
214 return !d;
215}
216
217QAuthenticatorPrivate::QAuthenticatorPrivate()
218 : ref(0)
219 , method(None)
220 , phase(Start)
221 , nonceCount(0)
222{
223 cnonce = QCryptographicHash::hash(QByteArray::number(qrand(), 16) + QByteArray::number(qrand(), 16),
224 QCryptographicHash::Md5).toHex();
225 nonceCount = 0;
226}
227
228#ifndef QT_NO_HTTP
229void QAuthenticatorPrivate::parseHttpResponse(const QHttpResponseHeader &header, bool isProxy)
230{
231 QList<QPair<QString, QString> > values = header.values();
232 const char *search = isProxy ? "proxy-authenticate" : "www-authenticate";
233
234 method = None;
235 /*
236 Fun from the HTTP 1.1 specs, that we currently ignore:
237
238 User agents are advised to take special care in parsing the WWW-
239 Authenticate field value as it might contain more than one challenge,
240 or if more than one WWW-Authenticate header field is provided, the
241 contents of a challenge itself can contain a comma-separated list of
242 authentication parameters.
243 */
244
245 QString headerVal;
246 for (int i = 0; i < values.size(); ++i) {
247 const QPair<QString, QString> &current = values.at(i);
248 if (current.first.toLower() != QLatin1String(search))
249 continue;
250 QString str = current.second;
251 if (method < Basic && str.startsWith(QLatin1String("Basic"), Qt::CaseInsensitive)) {
252 method = Basic; headerVal = str.mid(6);
253 } else if (method < Ntlm && str.startsWith(QLatin1String("NTLM"), Qt::CaseInsensitive)) {
254 method = Ntlm;
255 headerVal = str.mid(5);
256 } else if (method < DigestMd5 && str.startsWith(QLatin1String("Digest"), Qt::CaseInsensitive)) {
257 method = DigestMd5;
258 headerVal = str.mid(7);
259 }
260 }
261
262 challenge = headerVal.trimmed().toLatin1();
263 QHash<QByteArray, QByteArray> options = parseDigestAuthenticationChallenge(challenge);
264
265 switch(method) {
266 case Basic:
267 realm = QString::fromLatin1(options.value("realm"));
268 if (user.isEmpty())
269 phase = Done;
270 break;
271 case Ntlm:
272 // #### extract from header
273 realm = QString();
274 break;
275 case DigestMd5: {
276 realm = QString::fromLatin1(options.value("realm"));
277 if (options.value("stale").toLower() == "true")
278 phase = Start;
279 if (user.isEmpty())
280 phase = Done;
281 break;
282 }
283 default:
284 realm = QString();
285 challenge = QByteArray();
286 phase = Invalid;
287 }
288}
289#endif
290
291QByteArray QAuthenticatorPrivate::calculateResponse(const QByteArray &requestMethod, const QByteArray &path)
292{
293 QByteArray response;
294 const char *methodString = 0;
295 switch(method) {
296 case QAuthenticatorPrivate::None:
297 methodString = "";
298 phase = Done;
299 break;
300 case QAuthenticatorPrivate::Plain:
301 response = '\0' + user.toUtf8() + '\0' + password.toUtf8();
302 phase = Done;
303 break;
304 case QAuthenticatorPrivate::Basic:
305 methodString = "Basic ";
306 response = user.toLatin1() + ':' + password.toLatin1();
307 response = response.toBase64();
308 phase = Done;
309 break;
310 case QAuthenticatorPrivate::Login:
311 if (challenge.contains("VXNlciBOYW1lAA==")) {
312 response = user.toUtf8().toBase64();
313 phase = Phase2;
314 } else if (challenge.contains("UGFzc3dvcmQA")) {
315 response = password.toUtf8().toBase64();
316 phase = Done;
317 }
318 break;
319 case QAuthenticatorPrivate::CramMd5:
320 break;
321 case QAuthenticatorPrivate::DigestMd5:
322 methodString = "Digest ";
323 response = digestMd5Response(challenge, requestMethod, path);
324 phase = Done;
325 break;
326 case QAuthenticatorPrivate::Ntlm:
327 methodString = "NTLM ";
328 if (challenge.isEmpty()) {
329 response = qNtlmPhase1().toBase64();
330 if (user.isEmpty())
331 phase = Done;
332 else
333 phase = Phase2;
334 } else {
335 response = qNtlmPhase3(this, QByteArray::fromBase64(challenge)).toBase64();
336 phase = Done;
337 }
338
339 break;
340 }
341 return QByteArray(methodString) + response;
342}
343
344
345// ---------------------------- Digest Md5 code ----------------------------------------
346
347QHash<QByteArray, QByteArray> QAuthenticatorPrivate::parseDigestAuthenticationChallenge(const QByteArray &challenge)
348{
349 QHash<QByteArray, QByteArray> options;
350 // parse the challenge
351 const char *d = challenge.constData();
352 const char *end = d + challenge.length();
353 while (d < end) {
354 while (d < end && (*d == ' ' || *d == '\n' || *d == '\r'))
355 ++d;
356 const char *start = d;
357 while (d < end && *d != '=')
358 ++d;
359 QByteArray key = QByteArray(start, d - start);
360 ++d;
361 if (d >= end)
362 break;
363 bool quote = (*d == '"');
364 if (quote)
365 ++d;
366 if (d >= end)
367 break;
368 start = d;
369 QByteArray value;
370 while (d < end) {
371 bool backslash = false;
372 if (*d == '\\' && d < end - 1) {
373 ++d;
374 backslash = true;
375 }
376 if (!backslash) {
377 if (quote) {
378 if (*d == '"')
379 break;
380 } else {
381 if (*d == ',')
382 break;
383 }
384 }
385 value += *d;
386 ++d;
387 }
388 while (d < end && *d != ',')
389 ++d;
390 ++d;
391 options[key] = value;
392 }
393
394 QByteArray qop = options.value("qop");
395 if (!qop.isEmpty()) {
396 QList<QByteArray> qopoptions = qop.split(',');
397 if (!qopoptions.contains("auth"))
398 return QHash<QByteArray, QByteArray>();
399 // #### can't do auth-int currently
400// if (qop.contains("auth-int"))
401// qop = "auth-int";
402// else if (qop.contains("auth"))
403// qop = "auth";
404// else
405// qop = QByteArray();
406 options["qop"] = "auth";
407 }
408
409 return options;
410}
411
412/*
413 Digest MD5 implementation
414
415 Code taken from RFC 2617
416
417 Currently we don't support the full SASL authentication mechanism (which includes cyphers)
418*/
419
420
421/* calculate request-digest/response-digest as per HTTP Digest spec */
422static QByteArray digestMd5ResponseHelper(
423 const QByteArray &alg,
424 const QByteArray &userName,
425 const QByteArray &realm,
426 const QByteArray &password,
427 const QByteArray &nonce, /* nonce from server */
428 const QByteArray &nonceCount, /* 8 hex digits */
429 const QByteArray &cNonce, /* client nonce */
430 const QByteArray &qop, /* qop-value: "", "auth", "auth-int" */
431 const QByteArray &method, /* method from the request */
432 const QByteArray &digestUri, /* requested URL */
433 const QByteArray &hEntity /* H(entity body) if qop="auth-int" */
434 )
435{
436 QCryptographicHash hash(QCryptographicHash::Md5);
437 hash.addData(userName);
438 hash.addData(":", 1);
439 hash.addData(realm);
440 hash.addData(":", 1);
441 hash.addData(password);
442 QByteArray ha1 = hash.result();
443 if (alg.toLower() == "md5-sess") {
444 hash.reset();
445 // RFC 2617 contains an error, it was:
446 // hash.addData(ha1);
447 // but according to the errata page at http://www.rfc-editor.org/errata_list.php, ID 1649, it
448 // must be the following line:
449 hash.addData(ha1.toHex());
450 hash.addData(":", 1);
451 hash.addData(nonce);
452 hash.addData(":", 1);
453 hash.addData(cNonce);
454 ha1 = hash.result();
455 };
456 ha1 = ha1.toHex();
457
458 // calculate H(A2)
459 hash.reset();
460 hash.addData(method);
461 hash.addData(":", 1);
462 hash.addData(digestUri);
463 if (qop.toLower() == "auth-int") {
464 hash.addData(":", 1);
465 hash.addData(hEntity);
466 }
467 QByteArray ha2hex = hash.result().toHex();
468
469 // calculate response
470 hash.reset();
471 hash.addData(ha1);
472 hash.addData(":", 1);
473 hash.addData(nonce);
474 hash.addData(":", 1);
475 if (!qop.isNull()) {
476 hash.addData(nonceCount);
477 hash.addData(":", 1);
478 hash.addData(cNonce);
479 hash.addData(":", 1);
480 hash.addData(qop);
481 hash.addData(":", 1);
482 }
483 hash.addData(ha2hex);
484 return hash.result().toHex();
485}
486
487QByteArray QAuthenticatorPrivate::digestMd5Response(const QByteArray &challenge, const QByteArray &method, const QByteArray &path)
488{
489 QHash<QByteArray,QByteArray> options = parseDigestAuthenticationChallenge(challenge);
490
491 ++nonceCount;
492 QByteArray nonceCountString = QByteArray::number(nonceCount, 16);
493 while (nonceCountString.length() < 8)
494 nonceCountString.prepend('0');
495
496 QByteArray nonce = options.value("nonce");
497 QByteArray opaque = options.value("opaque");
498 QByteArray qop = options.value("qop");
499
500// qDebug() << "calculating digest: method=" << method << "path=" << path;
501 QByteArray response = digestMd5ResponseHelper(options.value("algorithm"), user.toLatin1(),
502 realm.toLatin1(), password.toLatin1(),
503 nonce, nonceCountString,
504 cnonce, qop, method,
505 path, QByteArray());
506
507
508 QByteArray credentials;
509 credentials += "username=\"" + user.toLatin1() + "\", ";
510 credentials += "realm=\"" + realm.toLatin1() + "\", ";
511 credentials += "nonce=\"" + nonce + "\", ";
512 credentials += "uri=\"" + path + "\", ";
513 if (!opaque.isEmpty())
514 credentials += "opaque=\"" + opaque + "\", ";
515 credentials += "response=\"" + response + '\"';
516 if (!options.value("algorithm").isEmpty())
517 credentials += ", algorithm=" + options.value("algorithm");
518 if (!options.value("qop").isEmpty()) {
519 credentials += ", qop=" + qop + ", ";
520 credentials += "nc=" + nonceCountString + ", ";
521 credentials += "cnonce=\"" + cnonce + '\"';
522 }
523
524 return credentials;
525}
526
527// ---------------------------- Digest Md5 code ----------------------------------------
528
529
530
531/*
532 * NTLM message flags.
533 *
534 * Copyright (c) 2004 Andrey Panin <[email protected]>
535 *
536 * This software is released under the MIT license.
537 */
538
539/*
540 * Indicates that Unicode strings are supported for use in security
541 * buffer data.
542 */
543#define NTLMSSP_NEGOTIATE_UNICODE 0x00000001
544
545/*
546 * Indicates that OEM strings are supported for use in security buffer data.
547 */
548#define NTLMSSP_NEGOTIATE_OEM 0x00000002
549
550/*
551 * Requests that the server's authentication realm be included in the
552 * Type 2 message.
553 */
554#define NTLMSSP_REQUEST_TARGET 0x00000004
555
556/*
557 * Specifies that authenticated communication between the client and server
558 * should carry a digital signature (message integrity).
559 */
560#define NTLMSSP_NEGOTIATE_SIGN 0x00000010
561
562/*
563 * Specifies that authenticated communication between the client and server
564 * should be encrypted (message confidentiality).
565 */
566#define NTLMSSP_NEGOTIATE_SEAL 0x00000020
567
568/*
569 * Indicates that datagram authentication is being used.
570 */
571#define NTLMSSP_NEGOTIATE_DATAGRAM 0x00000040
572
573/*
574 * Indicates that the LAN Manager session key should be
575 * used for signing and sealing authenticated communications.
576 */
577#define NTLMSSP_NEGOTIATE_LM_KEY 0x00000080
578
579/*
580 * Indicates that NTLM authentication is being used.
581 */
582#define NTLMSSP_NEGOTIATE_NTLM 0x00000200
583
584/*
585 * Sent by the client in the Type 1 message to indicate that the name of the
586 * domain in which the client workstation has membership is included in the
587 * message. This is used by the server to determine whether the client is
588 * eligible for local authentication.
589 */
590#define NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED 0x00001000
591
592/*
593 * Sent by the client in the Type 1 message to indicate that the client
594 * workstation's name is included in the message. This is used by the server
595 * to determine whether the client is eligible for local authentication.
596 */
597#define NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED 0x00002000
598
599/*
600 * Sent by the server to indicate that the server and client are on the same
601 * machine. Implies that the client may use the established local credentials
602 * for authentication instead of calculating a response to the challenge.
603 */
604#define NTLMSSP_NEGOTIATE_LOCAL_CALL 0x00004000
605
606/*
607 * Indicates that authenticated communication between the client and server
608 * should be signed with a "dummy" signature.
609 */
610#define NTLMSSP_NEGOTIATE_ALWAYS_SIGN 0x00008000
611
612/*
613 * Sent by the server in the Type 2 message to indicate that the target
614 * authentication realm is a domain.
615 */
616#define NTLMSSP_TARGET_TYPE_DOMAIN 0x00010000
617
618/*
619 * Sent by the server in the Type 2 message to indicate that the target
620 * authentication realm is a server.
621 */
622#define NTLMSSP_TARGET_TYPE_SERVER 0x00020000
623
624/*
625 * Sent by the server in the Type 2 message to indicate that the target
626 * authentication realm is a share. Presumably, this is for share-level
627 * authentication. Usage is unclear.
628 */
629#define NTLMSSP_TARGET_TYPE_SHARE 0x00040000
630
631/*
632 * Indicates that the NTLM2 signing and sealing scheme should be used for
633 * protecting authenticated communications. Note that this refers to a
634 * particular session security scheme, and is not related to the use of
635 * NTLMv2 authentication.
636 */
637#define NTLMSSP_NEGOTIATE_NTLM2 0x00080000
638
639/*
640 * Sent by the server in the Type 2 message to indicate that it is including
641 * a Target Information block in the message. The Target Information block
642 * is used in the calculation of the NTLMv2 response.
643 */
644#define NTLMSSP_NEGOTIATE_TARGET_INFO 0x00800000
645
646/*
647 * Indicates that 128-bit encryption is supported.
648 */
649#define NTLMSSP_NEGOTIATE_128 0x20000000
650
651/*
652 * Indicates that the client will provide an encrypted master session key in
653 * the "Session Key" field of the Type 3 message. This is used in signing and
654 * sealing, and is RC4-encrypted using the previous session key as the
655 * encryption key.
656 */
657#define NTLMSSP_NEGOTIATE_KEY_EXCHANGE 0x40000000
658
659/*
660 * Indicates that 56-bit encryption is supported.
661 */
662#define NTLMSSP_NEGOTIATE_56 0x80000000
663
664
665/* usage:
666 // fill up ctx with what we know.
667 QByteArray response = qNtlmPhase1(ctx);
668 // send response (b64 encoded??)
669 // get response from server (b64 decode?)
670 Phase2Block pb;
671 qNtlmDecodePhase2(response, pb);
672 response = qNtlmPhase3(ctx, pb);
673 // send response (b64 encoded??)
674*/
675
676/*
677 TODO:
678 - Fix unicode handling
679 - add v2 handling
680*/
681
682class QNtlmBuffer {
683public:
684 QNtlmBuffer() : len(0), maxLen(0), offset(0) {}
685 quint16 len;
686 quint16 maxLen;
687 quint32 offset;
688 enum { Size = 8 };
689};
690
691class QNtlmPhase1BlockBase
692{
693public:
694 char magic[8];
695 quint32 type;
696 quint32 flags;
697 QNtlmBuffer domain;
698 QNtlmBuffer workstation;
699 enum { Size = 32 };
700};
701
702// ################# check paddings
703class QNtlmPhase2BlockBase
704{
705public:
706 char magic[8];
707 quint32 type;
708 QNtlmBuffer targetName;
709 quint32 flags;
710 unsigned char challenge[8];
711 quint32 context[2];
712 QNtlmBuffer targetInfo;
713 enum { Size = 48 };
714};
715
716class QNtlmPhase3BlockBase {
717public:
718 char magic[8];
719 quint32 type;
720 QNtlmBuffer lmResponse;
721 QNtlmBuffer ntlmResponse;
722 QNtlmBuffer domain;
723 QNtlmBuffer user;
724 QNtlmBuffer workstation;
725 QNtlmBuffer sessionKey;
726 quint32 flags;
727 enum { Size = 64 };
728};
729
730static void qStreamNtlmBuffer(QDataStream& ds, const QByteArray& s)
731{
732 ds.writeRawData(s.constData(), s.size());
733}
734
735
736static void qStreamNtlmString(QDataStream& ds, const QString& s, bool unicode)
737{
738 if (!unicode) {
739 qStreamNtlmBuffer(ds, s.toLatin1());
740 return;
741 }
742 const ushort *d = s.utf16();
743 for (int i = 0; i < s.length(); ++i)
744 ds << d[i];
745}
746
747
748
749static int qEncodeNtlmBuffer(QNtlmBuffer& buf, int offset, const QByteArray& s)
750{
751 buf.len = s.size();
752 buf.maxLen = buf.len;
753 buf.offset = (offset + 1) & ~1;
754 return buf.offset + buf.len;
755}
756
757
758static int qEncodeNtlmString(QNtlmBuffer& buf, int offset, const QString& s, bool unicode)
759{
760 if (!unicode)
761 return qEncodeNtlmBuffer(buf, offset, s.toLatin1());
762 buf.len = 2 * s.length();
763 buf.maxLen = buf.len;
764 buf.offset = (offset + 1) & ~1;
765 return buf.offset + buf.len;
766}
767
768
769static QDataStream& operator<<(QDataStream& s, const QNtlmBuffer& b)
770{
771 s << b.len << b.maxLen << b.offset;
772 return s;
773}
774
775static QDataStream& operator>>(QDataStream& s, QNtlmBuffer& b)
776{
777 s >> b.len >> b.maxLen >> b.offset;
778 return s;
779}
780
781
782class QNtlmPhase1Block : public QNtlmPhase1BlockBase
783{ // request
784public:
785 QNtlmPhase1Block() {
786 qstrncpy(magic, "NTLMSSP", 8);
787 type = 1;
788 flags = NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_REQUEST_TARGET;
789 }
790
791 // extracted
792 QString domainStr, workstationStr;
793};
794
795
796class QNtlmPhase2Block : public QNtlmPhase2BlockBase
797{ // challenge
798public:
799 QNtlmPhase2Block() {
800 magic[0] = 0;
801 type = 0xffffffff;
802 }
803
804 // extracted
805 QString targetNameStr, targetInfoStr;
806};
807
808
809
810class QNtlmPhase3Block : public QNtlmPhase3BlockBase { // response
811public:
812 QNtlmPhase3Block() {
813 qstrncpy(magic, "NTLMSSP", 8);
814 type = 3;
815 flags = NTLMSSP_NEGOTIATE_UNICODE | NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_NEGOTIATE_TARGET_INFO;
816 }
817
818 // extracted
819 QByteArray lmResponseBuf, ntlmResponseBuf;
820 QString domainStr, userStr, workstationStr, sessionKeyStr;
821};
822
823
824static QDataStream& operator<<(QDataStream& s, const QNtlmPhase1Block& b) {
825 bool unicode = (b.flags & NTLMSSP_NEGOTIATE_UNICODE);
826
827 s.writeRawData(b.magic, sizeof(b.magic));
828 s << b.type;
829 s << b.flags;
830 s << b.domain;
831 s << b.workstation;
832 if (!b.domainStr.isEmpty())
833 qStreamNtlmString(s, b.domainStr, unicode);
834 if (!b.workstationStr.isEmpty())
835 qStreamNtlmString(s, b.workstationStr, unicode);
836 return s;
837}
838
839
840static QDataStream& operator<<(QDataStream& s, const QNtlmPhase3Block& b) {
841 bool unicode = (b.flags & NTLMSSP_NEGOTIATE_UNICODE);
842 s.writeRawData(b.magic, sizeof(b.magic));
843 s << b.type;
844 s << b.lmResponse;
845 s << b.ntlmResponse;
846 s << b.domain;
847 s << b.user;
848 s << b.workstation;
849 s << b.sessionKey;
850 s << b.flags;
851
852 if (!b.domainStr.isEmpty())
853 qStreamNtlmString(s, b.domainStr, unicode);
854
855 qStreamNtlmString(s, b.userStr, unicode);
856
857 if (!b.workstationStr.isEmpty())
858 qStreamNtlmString(s, b.workstationStr, unicode);
859
860 // Send auth info
861 qStreamNtlmBuffer(s, b.lmResponseBuf);
862 qStreamNtlmBuffer(s, b.ntlmResponseBuf);
863
864
865 return s;
866}
867
868
869static QByteArray qNtlmPhase1()
870{
871 QByteArray rc;
872 QDataStream ds(&rc, QIODevice::WriteOnly);
873 ds.setByteOrder(QDataStream::LittleEndian);
874 QNtlmPhase1Block pb;
875 ds << pb;
876 return rc;
877}
878
879
880static QByteArray qStringAsUcs2Le(const QString& src)
881{
882 QByteArray rc(2*src.length(), 0);
883 const unsigned short *s = src.utf16();
884 unsigned short *d = (unsigned short*)rc.data();
885 for (int i = 0; i < src.length(); ++i) {
886 d[i] = qToLittleEndian(s[i]);
887 }
888 return rc;
889}
890
891
892static QString qStringFromUcs2Le(const QByteArray& src)
893{
894 Q_ASSERT(src.size() % 2 == 0);
895 unsigned short *d = (unsigned short*)src.data();
896 for (int i = 0; i < src.length() / 2; ++i) {
897 d[i] = qFromLittleEndian(d[i]);
898 }
899 return QString((const QChar *)src.data(), src.size()/2);
900}
901
902
903static QByteArray qEncodeNtlmResponse(const QAuthenticatorPrivate *ctx, const QNtlmPhase2Block& ch)
904{
905 QCryptographicHash md4(QCryptographicHash::Md4);
906 QByteArray asUcs2Le = qStringAsUcs2Le(ctx->password);
907 md4.addData(asUcs2Le.data(), asUcs2Le.size());
908
909 unsigned char md4hash[22];
910 memset(md4hash, 0, sizeof(md4hash));
911 QByteArray hash = md4.result();
912 Q_ASSERT(hash.size() == 16);
913 memcpy(md4hash, hash.constData(), 16);
914
915 QByteArray rc(24, 0);
916 deshash((unsigned char *)rc.data(), md4hash, (unsigned char *)ch.challenge);
917 deshash((unsigned char *)rc.data() + 8, md4hash + 7, (unsigned char *)ch.challenge);
918 deshash((unsigned char *)rc.data() + 16, md4hash + 14, (unsigned char *)ch.challenge);
919
920 hash.fill(0);
921 return rc;
922}
923
924
925static QByteArray qEncodeLmResponse(const QAuthenticatorPrivate *ctx, const QNtlmPhase2Block& ch)
926{
927 QByteArray hash(21, 0);
928 QByteArray key(14, 0);
929 qstrncpy(key.data(), ctx->password.toUpper().toLatin1(), 14);
930 const char *block = "KGS!@#$%";
931
932 deshash((unsigned char *)hash.data(), (unsigned char *)key.data(), (unsigned char *)block);
933 deshash((unsigned char *)hash.data() + 8, (unsigned char *)key.data() + 7, (unsigned char *)block);
934 key.fill(0);
935
936 QByteArray rc(24, 0);
937 deshash((unsigned char *)rc.data(), (unsigned char *)hash.data(), ch.challenge);
938 deshash((unsigned char *)rc.data() + 8, (unsigned char *)hash.data() + 7, ch.challenge);
939 deshash((unsigned char *)rc.data() + 16, (unsigned char *)hash.data() + 14, ch.challenge);
940
941 hash.fill(0);
942 return rc;
943}
944
945
946static bool qNtlmDecodePhase2(const QByteArray& data, QNtlmPhase2Block& ch)
947{
948 Q_ASSERT(QNtlmPhase2BlockBase::Size == sizeof(QNtlmPhase2BlockBase));
949 if (data.size() < QNtlmPhase2BlockBase::Size)
950 return false;
951
952
953 QDataStream ds(data);
954 ds.setByteOrder(QDataStream::LittleEndian);
955 if (ds.readRawData(ch.magic, 8) < 8)
956 return false;
957 if (strncmp(ch.magic, "NTLMSSP", 8) != 0)
958 return false;
959
960 ds >> ch.type;
961 if (ch.type != 2)
962 return false;
963
964 ds >> ch.targetName;
965 ds >> ch.flags;
966 if (ds.readRawData((char *)ch.challenge, 8) < 8)
967 return false;
968 ds >> ch.context[0] >> ch.context[1];
969 ds >> ch.targetInfo;
970
971 if (ch.targetName.len > 0) {
972 if (ch.targetName.len + ch.targetName.offset >= (unsigned)data.size())
973 return false;
974
975 ch.targetNameStr = qStringFromUcs2Le(data.mid(ch.targetName.offset, ch.targetName.len));
976 }
977
978 if (ch.targetInfo.len > 0) {
979 // UNUSED right now
980 }
981
982 return true;
983}
984
985
986static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray& phase2data)
987{
988 QNtlmPhase2Block ch;
989 if (!qNtlmDecodePhase2(phase2data, ch))
990 return QByteArray();
991
992 QByteArray rc;
993 QDataStream ds(&rc, QIODevice::WriteOnly);
994 ds.setByteOrder(QDataStream::LittleEndian);
995 QNtlmPhase3Block pb;
996
997 bool unicode = ch.flags & NTLMSSP_NEGOTIATE_UNICODE;
998
999 ctx->realm = ch.targetNameStr;
1000
1001 pb.flags = NTLMSSP_NEGOTIATE_NTLM;
1002 if (unicode)
1003 pb.flags |= NTLMSSP_NEGOTIATE_UNICODE;
1004 else
1005 pb.flags |= NTLMSSP_NEGOTIATE_OEM;
1006
1007
1008 int offset = QNtlmPhase3BlockBase::Size;
1009 Q_ASSERT(QNtlmPhase3BlockBase::Size == sizeof(QNtlmPhase3BlockBase));
1010
1011 offset = qEncodeNtlmString(pb.domain, offset, ctx->realm, unicode);
1012 pb.domainStr = ctx->realm;
1013 offset = qEncodeNtlmString(pb.user, offset, ctx->user, unicode);
1014 pb.userStr = ctx->user;
1015
1016 offset = qEncodeNtlmString(pb.workstation, offset, ctx->workstation, unicode);
1017 pb.workstationStr = ctx->workstation;
1018
1019 // Get LM response
1020 pb.lmResponseBuf = qEncodeLmResponse(ctx, ch);
1021 offset = qEncodeNtlmBuffer(pb.lmResponse, offset, pb.lmResponseBuf);
1022
1023 // Get NTLM response
1024 pb.ntlmResponseBuf = qEncodeNtlmResponse(ctx, ch);
1025 offset = qEncodeNtlmBuffer(pb.ntlmResponse, offset, pb.ntlmResponseBuf);
1026
1027
1028 // Encode and send
1029 ds << pb;
1030
1031 return rc;
1032}
1033
1034
1035
1036QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.