5#include <qauthenticator.h>
6#include <qauthenticator_p.h>
8#include <qloggingcategory.h>
10#include <qbytearray.h>
11#include <qcryptographichash.h>
13#include <qdatastream.h>
18#include <QtNetwork/qhttpheaders.h>
26#define SECURITY_WIN32 1
28#elif QT_CONFIG(gssapi)
29#if defined(Q_OS_DARWIN)
32#include <gssapi/gssapi.h>
33#include <gssapi/gssapi_ext.h>
39using namespace Qt::StringLiterals;
47static bool q_SSPI_library_load();
48static QByteArray qSspiStartup(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method,
50static QByteArray qSspiContinue(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method,
51 QStringView host, QByteArrayView challenge = {});
52#elif QT_CONFIG(gssapi)
53static QByteArray qGssapiStartup(QAuthenticatorPrivate *ctx, QStringView host);
54static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx, QByteArrayView challenge = {});
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
171
172
173QAuthenticator::QAuthenticator()
179
180
181QAuthenticator::~QAuthenticator()
188
189
190QAuthenticator::QAuthenticator(
const QAuthenticator &other)
198
199
200QAuthenticator &QAuthenticator::operator=(
const QAuthenticator &other)
210 d->user = other.d->user;
211 d->userDomain = other.d->userDomain;
212 d->workstation = other.d->workstation;
213 d->extractedUser = other.d->extractedUser;
214 d->password = other.d->password;
215 d->realm = other.d->realm;
216 d->method = other.d->method;
217 d->options = other.d->options;
218 }
else if (d->phase == QAuthenticatorPrivate::Start) {
226
227
228
229bool QAuthenticator::operator==(
const QAuthenticator &other)
const
235 return d->user == other.d->user
236 && d->password == other.d->password
237 && d->realm == other.d->realm
238 && d->method == other.d->method
239 && d->options == other.d->options;
243
244
245
246
247
250
251
252QString QAuthenticator::user()
const
254 return d ? d->user : QString();
258
259
260
261
262void QAuthenticator::setUser(
const QString &user)
264 if (!d || d->user != user) {
267 d->updateCredentials();
272
273
274QString QAuthenticator::password()
const
276 return d ? d->password : QString();
280
281
282
283
284void QAuthenticator::setPassword(
const QString &password)
286 if (!d || d->password != password) {
288 d->password = password;
293
294
295void QAuthenticator::detach()
298 d =
new QAuthenticatorPrivate;
302 if (d->phase == QAuthenticatorPrivate::Done)
303 d->phase = QAuthenticatorPrivate::Start;
307
308
309QString QAuthenticator::realm()
const
311 return d ? d->realm : QString();
315
316
317void QAuthenticator::setRealm(
const QString &realm)
319 if (!d || d->realm != realm) {
326
327
328
329
330
331
332
333
334QVariant QAuthenticator::option(
const QString &opt)
const
336 return d ? d->options.value(opt) : QVariant();
340
341
342
343
344
345
346
347QVariantHash QAuthenticator::options()
const
349 return d ? d->options : QVariantHash();
353
354
355
356
357
358
359
360void QAuthenticator::setOption(
const QString &opt,
const QVariant &value)
362 if (option(opt) != value) {
364 d->options.insert(opt, value);
370
371
372
373
374
375bool QAuthenticator::isNull()
const
381
382
383
384
385
387void QAuthenticator::clear()
390 d =
new QAuthenticatorPrivate;
392 *d = QAuthenticatorPrivate();
394 d->phase = QAuthenticatorPrivate::Done;
398class QSSPIWindowsHandles
401 CredHandle credHandle;
402 CtxtHandle ctxHandle;
404#elif QT_CONFIG(gssapi)
408 Q_DISABLE_COPY_MOVE(QGssApiHandles)
409 QGssApiHandles() =
default;
412 OM_uint32 ignored = 0;
414 gss_release_name(&ignored, &targetName);
416 gss_delete_sec_context(&ignored, &gssCtx, GSS_C_NO_BUFFER);
419 gss_ctx_id_t gssCtx =
nullptr;
420 gss_name_t targetName =
nullptr;
425QAuthenticatorPrivate::QAuthenticatorPrivate()
431 cnonce = QCryptographicHash::hash(QByteArray::number(QRandomGenerator::system()->generate64(), 16),
432 QCryptographicHash::Md5).toHex();
436QAuthenticatorPrivate::~QAuthenticatorPrivate() =
default;
438void QAuthenticatorPrivate::updateCredentials()
440 int separatorPosn = 0;
443 case QAuthenticatorPrivate::Ntlm:
444 if ((separatorPosn = user.indexOf(
"\\"_L1)) != -1) {
447 userDomain = user.left(separatorPosn);
448 extractedUser = user.mid(separatorPosn + 1);
450 extractedUser = user;
461bool QAuthenticatorPrivate::isMethodSupported(QByteArrayView method)
463 Q_ASSERT(!method.startsWith(
' '));
464 auto separator = method.indexOf(
' ');
466 method = method.first(separator);
467 const auto isSupported = [method](QByteArrayView reference) {
468 return method.compare(reference, Qt::CaseInsensitive) == 0;
470 static const char methods[][10] = {
474#if QT_CONFIG(sspi) || QT_CONFIG(gssapi)
478 return std::any_of(methods, methods + std::size(methods), isSupported);
483 auto opts = QAuthenticatorPrivate::parseDigestAuthenticationChallenge(value);
484 if (
auto it = opts.constFind(
"algorithm"); it != opts.cend()) {
490 auto view = QByteArrayView(alg).first(3);
491 return view.compare(
"MD5", Qt::CaseInsensitive) == 0;
497
498
499
500
501
502
503
504
509 case QAuthenticatorPrivate::None:
return 0;
510 case QAuthenticatorPrivate::Basic:
return 1;
511 case QAuthenticatorPrivate::DigestMd5:
return 2;
512 case QAuthenticatorPrivate::Ntlm:
return 3;
513 case QAuthenticatorPrivate::Negotiate:
return 4;
516 Q_UNREACHABLE_RETURN(0);
519static const char *
methodName(QAuthenticatorPrivate::Method method)
522 case QAuthenticatorPrivate::None:
return "None";
523 case QAuthenticatorPrivate::Basic:
return "Basic";
524 case QAuthenticatorPrivate::DigestMd5:
return "Digest-MD5";
525 case QAuthenticatorPrivate::Ntlm:
return "NTLM";
526 case QAuthenticatorPrivate::Negotiate:
return "Negotiate";
529 Q_UNREACHABLE_RETURN(
"Unknown");
533void QAuthenticatorPrivate::parseHttpResponse(
const QHttpHeaders &headers,
536 const auto search = isProxy ? QHttpHeaders::WellKnownHeader::ProxyAuthenticate
537 : QHttpHeaders::WellKnownHeader::WWWAuthenticate;
539 const Method previousMethod = method;
540 const Phase previousPhase = phase;
543
544
545
546
547
548
549
550
552 QByteArrayView headerVal;
553 for (
const auto ¤t : headers.values(search)) {
554 const QLatin1StringView str(current);
555 if (method < Basic && str.startsWith(
"basic"_L1, Qt::CaseInsensitive)) {
557 headerVal = QByteArrayView(current).mid(6);
558 }
else if (method < Ntlm && str.startsWith(
"ntlm"_L1, Qt::CaseInsensitive)) {
560 headerVal = QByteArrayView(current).mid(5);
561 }
else if (method < DigestMd5 && str.startsWith(
"digest"_L1, Qt::CaseInsensitive)) {
563 if (!verifyDigestMD5(QByteArrayView(current).sliced(7)))
567 headerVal = QByteArrayView(current).mid(7);
568 }
else if (method < Negotiate && str.startsWith(
"negotiate"_L1, Qt::CaseInsensitive)) {
569#if QT_CONFIG(sspi) || QT_CONFIG(gssapi)
571 headerVal = QByteArrayView(current).mid(10);
578 if (previousPhase == Phase2
579 && methodStrength(method) < methodStrength(previousMethod)) {
581 "Authentication method downgrade from %s to %s refused "
582 "during multi-round exchange (possible man-in-the-middle). "
583 "Aborting authentication.",
584 methodName(previousMethod), methodName(method));
588 challenge = QByteArray();
594 challenge = headerVal.trimmed().toByteArray();
595 QHash<QByteArray, QByteArray> options = parseDigestAuthenticationChallenge(challenge);
599 auto privSetRealm = [
this](QString newRealm) {
600 if (newRealm != realm) {
604 this->options[
"realm"_L1] = realm;
610 privSetRealm(QString::fromLatin1(options.value(
"realm")));
611 if (user.isEmpty() && password.isEmpty())
619 privSetRealm(QString::fromLatin1(options.value(
"realm")));
620 if (options.value(
"stale").compare(
"true", Qt::CaseInsensitive) == 0) {
624 if (user.isEmpty() && password.isEmpty())
630 challenge = QByteArray();
635QByteArray QAuthenticatorPrivate::calculateResponse(QByteArrayView requestMethod,
636 QByteArrayView path, QStringView host)
638#if !QT_CONFIG(sspi) && !QT_CONFIG(gssapi)
642 QByteArrayView methodString;
644 case QAuthenticatorPrivate::None:
647 case QAuthenticatorPrivate::Basic:
648 methodString =
"Basic";
649 response = (user +
':'_L1 + password).toLatin1().toBase64();
652 case QAuthenticatorPrivate::DigestMd5:
653 methodString =
"Digest";
654 response = digestMd5Response(challenge, requestMethod, path);
657 case QAuthenticatorPrivate::Ntlm:
658 methodString =
"NTLM";
659 if (challenge.isEmpty()) {
661 QByteArray phase1Token;
662 if (user.isEmpty()) {
663 phase1Token = qSspiStartup(
this, method, host);
664 }
else if (!q_SSPI_library_load()) {
666 qWarning(
"Failed to load the SSPI libraries");
669 if (!phase1Token.isEmpty()) {
670 response = phase1Token.toBase64();
675 response = qNtlmPhase1().toBase64();
683 QByteArray phase3Token;
684 if (sspiWindowsHandles)
685 phase3Token = qSspiContinue(
this, method, host, QByteArray::fromBase64(challenge));
686 if (!phase3Token.isEmpty()) {
687 response = phase3Token.toBase64();
692 response = qNtlmPhase3(
this, QByteArray::fromBase64(challenge)).toBase64();
699 case QAuthenticatorPrivate::Negotiate:
700 methodString =
"Negotiate";
701 if (challenge.isEmpty()) {
702 QByteArray phase1Token;
704 phase1Token = qSspiStartup(
this, method, host);
705#elif QT_CONFIG(gssapi)
706 phase1Token = qGssapiStartup(
this, host);
709 if (!phase1Token.isEmpty()) {
710 response = phase1Token.toBase64();
717 QByteArray phase3Token;
719 if (sspiWindowsHandles)
720 phase3Token = qSspiContinue(
this, method, host, QByteArray::fromBase64(challenge));
721#elif QT_CONFIG(gssapi)
723 phase3Token = qGssapiContinue(
this, QByteArray::fromBase64(challenge));
725 if (!phase3Token.isEmpty()) {
726 response = phase3Token.toBase64();
738 return methodString +
' ' + response;
746 for (
auto element : QLatin1StringView(data).tokenize(
','_L1)) {
747 if (element ==
"auth"_L1)
753QHash<QByteArray, QByteArray>
754QAuthenticatorPrivate::parseDigestAuthenticationChallenge(QByteArrayView challenge)
756 QHash<QByteArray, QByteArray> options;
758 const char *d = challenge.data();
759 const char *end = d + challenge.size();
761 while (d < end && (*d ==
' ' || *d ==
'\n' || *d ==
'\r'))
763 const char *start = d;
764 while (d < end && *d !=
'=')
768 QByteArrayView key = QByteArrayView(start, d - start);
772 bool quote = (*d ==
'"');
779 bool backslash =
false;
780 if (*d ==
'\\' && d < end - 1) {
796 while (d < end && *d !=
',')
800 options[key.toByteArray()] = std::move(value);
803 QByteArray qop = options.value(
"qop");
804 if (!qop.isEmpty()) {
805 if (!containsAuth(qop))
806 return QHash<QByteArray, QByteArray>();
814 options[
"qop"] =
"auth";
821
822
823
824
825
826
832 QByteArrayView userName,
833 QByteArrayView realm,
834 QByteArrayView password,
835 QByteArrayView nonce,
836 QByteArrayView nonceCount,
837 QByteArrayView cNonce,
839 QByteArrayView method,
840 QByteArrayView digestUri,
841 QByteArrayView hEntity
845 hash.addData(userName);
849 hash.addData(password);
851 if (alg.compare(
"md5-sess", Qt::CaseInsensitive) == 0) {
857 hash.addData(ha1.toHex());
861 hash.addData(cNonce);
868 hash.addData(method);
870 hash.addData(digestUri);
871 if (qop.compare(
"auth-int", Qt::CaseInsensitive) == 0) {
873 hash.addData(hEntity);
884 hash.addData(nonceCount);
886 hash.addData(cNonce);
891 hash.addData(ha2hex);
892 return hash.result().toHex();
895QByteArray QAuthenticatorPrivate::digestMd5Response(QByteArrayView challenge, QByteArrayView method,
898 QHash<QByteArray,QByteArray> options = parseDigestAuthenticationChallenge(challenge);
901 QByteArray nonceCountString = QByteArray::number(nonceCount, 16);
902 while (nonceCountString.size() < 8)
903 nonceCountString.prepend(
'0');
905 QByteArray nonce = options.value(
"nonce");
906 QByteArray opaque = options.value(
"opaque");
907 QByteArray qop = options.value(
"qop");
910 QByteArray response = digestMd5ResponseHelper(options.value(
"algorithm"), user.toLatin1(),
911 realm.toLatin1(), password.toLatin1(),
912 nonce, nonceCountString,
917 QByteArray credentials;
918 credentials +=
"username=\"" + user.toLatin1() +
"\", ";
919 credentials +=
"realm=\"" + realm.toLatin1() +
"\", ";
920 credentials +=
"nonce=\"" + nonce +
"\", ";
921 credentials +=
"uri=\"" + path +
"\", ";
922 if (!opaque.isEmpty())
923 credentials +=
"opaque=\"" + opaque +
"\", ";
924 credentials +=
"response=\"" + response +
'"';
925 if (!options.value(
"algorithm").isEmpty())
926 credentials +=
", algorithm=" + options.value(
"algorithm");
927 if (!options.value(
"qop").isEmpty()) {
928 credentials +=
", qop=" + qop +
", ";
929 credentials +=
"nc=" + nonceCountString +
", ";
930 credentials +=
"cnonce=\"" + cnonce +
'"';
942
943
944
945
946
947
950
951
952
953#define NTLMSSP_NEGOTIATE_UNICODE 0x00000001
956
957
958#define NTLMSSP_NEGOTIATE_OEM 0x00000002
961
962
963
964#define NTLMSSP_REQUEST_TARGET 0x00000004
967
968
969
970#define NTLMSSP_NEGOTIATE_SIGN 0x00000010
973
974
975
976#define NTLMSSP_NEGOTIATE_SEAL 0x00000020
979
980
981#define NTLMSSP_NEGOTIATE_DATAGRAM 0x00000040
984
985
986
987#define NTLMSSP_NEGOTIATE_LM_KEY 0x00000080
990
991
992#define NTLMSSP_NEGOTIATE_NTLM 0x00000200
995
996
997
998
999
1000#define NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED 0x00001000
1003
1004
1005
1006
1007#define NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED 0x00002000
1010
1011
1012
1013
1014#define NTLMSSP_NEGOTIATE_LOCAL_CALL 0x00004000
1017
1018
1019
1020#define NTLMSSP_NEGOTIATE_ALWAYS_SIGN 0x00008000
1023
1024
1025
1026#define NTLMSSP_TARGET_TYPE_DOMAIN 0x00010000
1029
1030
1031
1032#define NTLMSSP_TARGET_TYPE_SERVER 0x00020000
1035
1036
1037
1038
1039#define NTLMSSP_TARGET_TYPE_SHARE 0x00040000
1042
1043
1044
1045
1046
1047#define NTLMSSP_NEGOTIATE_NTLM2 0x00080000
1050
1051
1052
1053
1054#define NTLMSSP_NEGOTIATE_TARGET_INFO 0x00800000
1057
1058
1059#define NTLMSSP_NEGOTIATE_128 0x20000000
1062
1063
1064
1065
1066
1067#define NTLMSSP_NEGOTIATE_KEY_EXCHANGE 0x40000000
1070
1071
1072#define NTLMSSP_NEGOTIATE_56 0x80000000
1075
1076
1077#define AVTIMESTAMP 7
1089
1090
1091
1092
1093
1094
1095
1096
1097
1110 ds.writeRawData(s.constData(), s.size());
1117 qStreamNtlmBuffer(ds, s.toLatin1());
1122 ds << quint16(ch.unicode());
1130 buf.maxLen = buf.len;
1131 buf.offset = (offset + 1) & ~1;
1132 return buf.offset + buf.len;
1139 return qEncodeNtlmBuffer(buf, offset, s.toLatin1());
1140 buf.len = 2 * s.size();
1141 buf.maxLen = buf.len;
1142 buf.offset = (offset + 1) & ~1;
1143 return buf.offset + buf.len;
1149 s << b.len << b.maxLen << b.offset;
1155 s >> b.len >> b.maxLen >> b.offset;
1163 char magic[8] = {
'N',
'T',
'L',
'M',
'S',
'S',
'P',
'\0'};
1196 char magic[8] = {
'N',
'T',
'L',
'M',
'S',
'S',
'P',
'\0'};
1222 if (!b.domainStr.isEmpty())
1223 qStreamNtlmString(s, b.domainStr, unicode);
1224 if (!b.workstationStr.isEmpty())
1225 qStreamNtlmString(s, b.workstationStr, unicode);
1235 s << b.ntlmResponse;
1242 if (!b.domainStr.isEmpty())
1243 qStreamNtlmString(s, b.domainStr, unicode);
1245 qStreamNtlmString(s, b.userStr, unicode);
1247 if (!b.workstationStr.isEmpty())
1248 qStreamNtlmString(s, b.workstationStr, unicode);
1251 qStreamNtlmBuffer(s, b.lmResponseBuf);
1252 qStreamNtlmBuffer(s, b.ntlmResponseBuf);
1262 QDataStream ds(&rc, QIODevice::WriteOnly);
1263 ds.setByteOrder(QDataStream::LittleEndian);
1273 unsigned short *d = (
unsigned short*)rc.data();
1274 for (QChar ch : src)
1275 *d++ = qToLittleEndian(quint16(ch.unicode()));
1283 Q_ASSERT(src.size() % 2 == 0);
1284 unsigned short *d = (
unsigned short*)src.data();
1285 for (
int i = 0; i < src.size() / 2; ++i) {
1286 d[i] = qFromLittleEndian(d[i]);
1288 return QString((
const QChar *)src.data(), src.size()/2);
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1316 Q_ASSERT_X(!(message.isEmpty()),
"qEncodeHmacMd5",
"Empty message check");
1317 Q_ASSERT_X(!(key.isEmpty()),
"qEncodeHmacMd5",
"Empty key check");
1329 key = hash.result();
1334 key = key.leftJustified(
blockSize,0,
true);
1339 for(
int i = 0; i<key.size();i++) {
1340 iKeyPad[i] = key[i]^iKeyPad[i];
1344 for(
int i = 0; i<key.size();i++) {
1345 oKeyPad[i] = key[i]^oKeyPad[i];
1348 iKeyPad.append(message);
1351 hash.addData(iKeyPad);
1352 QByteArrayView hMsg = hash.resultView();
1356 oKeyPad.append(hMsg);
1358 hash.addData(oKeyPad);
1359 hmacDigest = hash.result();
1363
1364
1365
1366
1369
1376 Q_ASSERT(phase3 !=
nullptr);
1379 if (phase3->v2Hash.size() == 0) {
1381 QByteArray passUnicode = qStringAsUcs2Le(ctx->password);
1382 md4.addData(passUnicode);
1385 Q_ASSERT(hashKey.size() == 16);
1388 qStringAsUcs2Le(ctx->extractedUser.toUpper()) +
1389 qStringAsUcs2Le(phase3->domainStr);
1391 phase3->v2Hash = qEncodeHmacMd5(hashKey, message);
1393 return phase3->v2Hash;
1398 Q_ASSERT(ctx->cnonce.size() >= 8);
1407 const char *ptr = targetInfoBuff.constBegin();
1408 const char *end = targetInfoBuff.constEnd();
1412 while (end - ptr >= 4) {
1413 avId = qFromLittleEndian<quint16>(ptr + 0);
1414 avLen = qFromLittleEndian<quint16>(ptr + 2);
1418 if (avLen != NtlmFileTimeSize)
1423 timeArray.assign(ptr, ptr + NtlmFileTimeSize);
1427 if (avLen > end - ptr)
1438 Q_ASSERT(phase3 !=
nullptr);
1443 QDataStream ds(&temp, QIODevice::WriteOnly);
1444 ds.setByteOrder(QDataStream::LittleEndian);
1447 ds << hirespversion;
1451 ds.writeRawData(reserved1.constData(), reserved1.size());
1456 if (ch.targetInfo.len)
1458 timeArray = qExtractServerTime(ch.targetInfoBuff);
1462 if (timeArray.size()) {
1463 ds.writeRawData(timeArray.constData(), timeArray.size());
1468 time = QDateTime::currentSecsSinceEpoch() + 11644473600;
1471 time = time * Q_UINT64_C(10000000);
1477 ds.writeRawData(clientCh.constData(), clientCh.size());
1481 ds.writeRawData(reserved2.constData(), reserved2.size());
1483 if (ch.targetInfo.len > 0) {
1484 ds.writeRawData(ch.targetInfoBuff.constData(),
1485 ch.targetInfoBuff.size());
1490 ds.writeRawData(reserved3.constData(), reserved3.size());
1493 message.append(temp);
1495 QByteArray ntChallengeResp = qEncodeHmacMd5(phase3->v2Hash, message);
1496 ntChallengeResp.append(temp);
1498 return ntChallengeResp;
1505 Q_ASSERT(phase3 !=
nullptr);
1512 message.append(clientCh);
1514 QByteArray lmChallengeResp = qEncodeHmacMd5(phase3->v2Hash, message);
1515 lmChallengeResp.append(clientCh);
1517 return lmChallengeResp;
1526 QDataStream ds(data);
1527 ds.setByteOrder(QDataStream::LittleEndian);
1528 if (ds.readRawData(ch
.magic, 8) < 8)
1530 if (strncmp(ch
.magic,
"NTLMSSP", 8) != 0)
1537 ds >> ch.targetName;
1539 if (ds.readRawData((
char *)ch
.challenge, 8) < 8)
1541 ds >> ch.context[0] >> ch.context[1];
1542 ds >> ch.targetInfo;
1544 if (ch.targetName.len > 0) {
1546 if (qAddOverflow(qsizetype(ch.targetName.offset), qsizetype(ch.targetName.len), &total))
1548 if (total > data.size())
1551 ch.targetNameStr = qStringFromUcs2Le(data.mid(ch.targetName.offset, ch.targetName.len));
1554 if (ch.targetInfo.len > 0) {
1556 if (qAddOverflow(qsizetype(ch.targetInfo.offset), qsizetype(ch.targetInfo.len), &total))
1558 if (total > data.size())
1561 ch.targetInfoBuff = data.mid(ch.targetInfo.offset, ch.targetInfo.len);
1575 QDataStream ds(&rc, QIODevice::WriteOnly);
1576 ds.setByteOrder(QDataStream::LittleEndian);
1598 if (ctx->userDomain.isEmpty() && !ctx->extractedUser.contains(u'@')) {
1599 offset = qEncodeNtlmString(pb.domain, offset, ch.targetNameStr, unicode);
1600 pb.domainStr = ch.targetNameStr;
1602 offset = qEncodeNtlmString(pb.domain, offset, ctx->userDomain, unicode);
1603 pb.domainStr = ctx->userDomain;
1606 offset = qEncodeNtlmString(pb.user, offset, ctx->extractedUser, unicode);
1607 pb.userStr = ctx->extractedUser;
1609 offset = qEncodeNtlmString(pb.workstation, offset, ctx->workstation, unicode);
1610 pb.workstationStr = ctx->workstation;
1613 if (ch.targetInfo.len > 0) {
1618 offset = qEncodeNtlmBuffer(pb.lmResponse, offset, pb.lmResponseBuf);
1622 offset = qEncodeNtlmBuffer(pb.ntlmResponse, offset, pb.ntlmResponseBuf);
1639static PSecurityFunctionTableW pSecurityFunctionTable =
nullptr;
1641static bool q_SSPI_library_load()
1643 Q_CONSTINIT
static QBasicMutex mutex;
1644 QMutexLocker l(&mutex);
1646 if (pSecurityFunctionTable ==
nullptr)
1647 pSecurityFunctionTable = InitSecurityInterfaceW();
1649 if (pSecurityFunctionTable ==
nullptr)
1655static QByteArray qSspiStartup(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method,
1658 if (!q_SSPI_library_load())
1659 return QByteArray();
1663 if (!ctx->sspiWindowsHandles)
1664 ctx->sspiWindowsHandles.reset(
new QSSPIWindowsHandles);
1665 SecInvalidateHandle(&ctx->sspiWindowsHandles->credHandle);
1666 SecInvalidateHandle(&ctx->sspiWindowsHandles->ctxHandle);
1668 SEC_WINNT_AUTH_IDENTITY auth;
1669 auth.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
1670 bool useAuth =
false;
1671 if (method == QAuthenticatorPrivate::Negotiate && !ctx->user.isEmpty()) {
1672 auth.Domain =
const_cast<ushort *>(
reinterpret_cast<
const ushort *>(ctx->userDomain.constData()));
1673 auth.DomainLength = ctx->userDomain.size();
1674 auth.User =
const_cast<ushort *>(
reinterpret_cast<
const ushort *>(ctx->user.constData()));
1675 auth.UserLength = ctx->user.size();
1676 auth.Password =
const_cast<ushort *>(
reinterpret_cast<
const ushort *>(ctx->password.constData()));
1677 auth.PasswordLength = ctx->password.size();
1682 SECURITY_STATUS secStatus = pSecurityFunctionTable->AcquireCredentialsHandle(
1684 (SEC_WCHAR *)(method == QAuthenticatorPrivate::Negotiate ? L"Negotiate" : L"NTLM"),
1685 SECPKG_CRED_OUTBOUND,
nullptr, useAuth ? &auth :
nullptr,
nullptr,
nullptr,
1686 &ctx->sspiWindowsHandles->credHandle, &expiry
1688 if (secStatus != SEC_E_OK) {
1689 ctx->sspiWindowsHandles.reset(
nullptr);
1690 return QByteArray();
1693 return qSspiContinue(ctx, method, host);
1696static QByteArray qSspiContinue(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method,
1697 QStringView host, QByteArrayView challenge)
1700 SecBuffer challengeBuf;
1701 SecBuffer responseBuf;
1702 SecBufferDesc challengeDesc;
1703 SecBufferDesc responseDesc;
1704 unsigned long attrs;
1707 if (!challenge.isEmpty())
1710 challengeDesc.ulVersion = SECBUFFER_VERSION;
1711 challengeDesc.cBuffers = 1;
1712 challengeDesc.pBuffers = &challengeBuf;
1713 challengeBuf.BufferType = SECBUFFER_TOKEN;
1714 challengeBuf.pvBuffer = (PVOID)(challenge.data());
1715 challengeBuf.cbBuffer = challenge.length();
1719 responseDesc.ulVersion = SECBUFFER_VERSION;
1720 responseDesc.cBuffers = 1;
1721 responseDesc.pBuffers = &responseBuf;
1722 responseBuf.BufferType = SECBUFFER_TOKEN;
1723 responseBuf.pvBuffer =
nullptr;
1724 responseBuf.cbBuffer = 0;
1727 QString targetName = ctx->options.value(
"spn"_L1).toString();
1728 if (targetName.isEmpty())
1729 targetName =
"HTTP/"_L1 + host;
1730 const std::wstring targetNameW = (method == QAuthenticatorPrivate::Negotiate
1731 ? targetName : QString()).toStdWString();
1734 SECURITY_STATUS secStatus = pSecurityFunctionTable->InitializeSecurityContext(
1735 &ctx->sspiWindowsHandles->credHandle,
1736 !challenge.isEmpty() ? &ctx->sspiWindowsHandles->ctxHandle :
nullptr,
1737 const_cast<
wchar_t*>(targetNameW.data()),
1738 ISC_REQ_ALLOCATE_MEMORY,
1739 0, SECURITY_NATIVE_DREP,
1740 !challenge.isEmpty() ? &challengeDesc :
nullptr,
1741 0, &ctx->sspiWindowsHandles->ctxHandle,
1742 &responseDesc, &attrs,
1746 if (secStatus == SEC_I_COMPLETE_NEEDED || secStatus == SEC_I_COMPLETE_AND_CONTINUE) {
1747 secStatus = pSecurityFunctionTable->CompleteAuthToken(&ctx->sspiWindowsHandles->ctxHandle,
1751 if (secStatus != SEC_I_COMPLETE_AND_CONTINUE && secStatus != SEC_I_CONTINUE_NEEDED) {
1752 pSecurityFunctionTable->FreeCredentialsHandle(&ctx->sspiWindowsHandles->credHandle);
1753 pSecurityFunctionTable->DeleteSecurityContext(&ctx->sspiWindowsHandles->ctxHandle);
1754 ctx->sspiWindowsHandles.reset(
nullptr);
1757 result = QByteArray((
const char*)responseBuf.pvBuffer, responseBuf.cbBuffer);
1758 pSecurityFunctionTable->FreeContextBuffer(responseBuf.pvBuffer);
1765#elif QT_CONFIG(gssapi)
1771static void q_GSSAPI_error_int(
const char *message, OM_uint32 stat,
int type)
1773 OM_uint32 minStat, msgCtx = 0;
1774 gss_buffer_desc msg;
1777 gss_display_status(&minStat, stat, type, GSS_C_NO_OID, &msgCtx, &msg);
1778 qCDebug(lcAuthenticator) << message <<
": " <<
reinterpret_cast<
const char*>(msg.value);
1779 gss_release_buffer(&minStat, &msg);
1784static void q_GSSAPI_error(
const char *message, OM_uint32 majStat, OM_uint32 minStat)
1787 q_GSSAPI_error_int(message, majStat, GSS_C_GSS_CODE);
1790 q_GSSAPI_error_int(message, minStat, GSS_C_MECH_CODE);
1793static gss_name_t qGSsapiGetServiceName(QStringView host)
1795 QByteArray serviceName =
"HTTPS@" + host.toLocal8Bit();
1796 gss_buffer_desc nameDesc = {
static_cast<std::size_t>(serviceName.size()), serviceName.data()};
1798 gss_name_t importedName;
1800 OM_uint32 majStat = gss_import_name(&minStat, &nameDesc,
1801 GSS_C_NT_HOSTBASED_SERVICE, &importedName);
1803 if (majStat != GSS_S_COMPLETE) {
1804 q_GSSAPI_error(
"gss_import_name error", majStat, minStat);
1807 return importedName;
1811static QByteArray qGssapiStartup(QAuthenticatorPrivate *ctx, QStringView host)
1813 if (!ctx->gssApiHandles)
1814 ctx->gssApiHandles.reset(
new QGssApiHandles);
1817 gss_name_t name = qGSsapiGetServiceName(host);
1818 if (name ==
nullptr) {
1819 ctx->gssApiHandles.reset(
nullptr);
1820 return QByteArray();
1822 ctx->gssApiHandles->targetName = name;
1825 ctx->gssApiHandles->gssCtx = GSS_C_NO_CONTEXT;
1826 return qGssapiContinue(ctx);
1829static gss_cred_id_t qGssapiCreateCredentials(
const QByteArray &realm,
1830 const QByteArray &username,
1831 const QByteArray &password)
1835 QByteArray qualified = username;
1836 if (!realm.isEmpty())
1837 qualified +=
'@' + realm;
1838 gss_buffer_desc nameBuffer {
1839 size_t(qualified.size()),
1842 gss_name_t importedName;
1843 OM_uint32 majStat = gss_import_name(&minStat, &nameBuffer, GSS_C_NT_USER_NAME, &importedName);
1844 if (majStat != GSS_S_COMPLETE) {
1845 q_GSSAPI_error(
"gss_import_name error", majStat, minStat);
1849 gss_buffer_desc passwordBuffer {
1850 size_t(password.size()),
1851 const_cast<
char *>(password.data())
1853 gss_cred_id_t credHandle;
1854 majStat = gss_acquire_cred_with_password(&minStat, importedName, &passwordBuffer,
1855 GSS_C_INDEFINITE, GSS_C_NO_OID_SET,
1856 GSS_C_INITIATE, &credHandle,
1860 gss_release_name(&ignored, &importedName);
1862 if (majStat != GSS_S_COMPLETE) {
1863 q_GSSAPI_error(
"gss_acquire_cred_with_password error", majStat, minStat);
1870static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx, QByteArrayView challenge)
1872 OM_uint32 majStat, minStat, ignored;
1874 gss_buffer_desc inBuf = {0,
nullptr};
1875 gss_buffer_desc outBuf;
1877 if (!challenge.isEmpty()) {
1878 inBuf.value =
const_cast<
char*>(challenge.data());
1879 inBuf.length = challenge.size();
1882 gss_cred_id_t credHandle = GSS_C_NO_CREDENTIAL;
1883 if (!ctx->user.isEmpty()) {
1884 credHandle = qGssapiCreateCredentials(ctx->userDomain.toLocal8Bit(),
1885 ctx->user.toLocal8Bit(),
1886 ctx->password.toLocal8Bit());
1887 if (credHandle ==
nullptr) {
1888 ctx->gssApiHandles.reset(
nullptr);
1893 majStat = gss_init_sec_context(&minStat,
1895 &ctx->gssApiHandles->gssCtx,
1896 ctx->gssApiHandles->targetName,
1900 GSS_C_NO_CHANNEL_BINDINGS,
1901 challenge.isEmpty() ? GSS_C_NO_BUFFER : &inBuf,
1906 gss_release_cred(&ignored, &credHandle);
1908 if (outBuf.length != 0)
1909 result = QByteArray(
reinterpret_cast<
const char*>(outBuf.value), outBuf.length);
1910 gss_release_buffer(&ignored, &outBuf);
1912 if (majStat != GSS_S_CONTINUE_NEEDED) {
1913 if (majStat != GSS_S_COMPLETE)
1914 q_GSSAPI_error(
"gss_init_sec_context error", majStat, minStat);
1915 ctx->gssApiHandles.reset(
nullptr);
1927#include "moc_qauthenticator.cpp"
QByteArray targetInfoBuff
unsigned char challenge[8]
QByteArray ntlmResponseBuf
Combined button and popup list for selecting options.
static QByteArray clientChallenge(const QAuthenticatorPrivate *ctx)
static QByteArray qNtlmPhase1()
#define NTLMSSP_NEGOTIATE_NTLM2
#define NTLMSSP_NEGOTIATE_TARGET_INFO
static constexpr quint16 NtlmFileTimeSize
static QByteArray qStringAsUcs2Le(const QString &src)
static QByteArray qEncodeLmv2Response(const QAuthenticatorPrivate *ctx, const QNtlmPhase2Block &ch, QNtlmPhase3Block *phase3)
static bool verifyDigestMD5(QByteArrayView value)
static bool containsAuth(QByteArrayView data)
static int qEncodeNtlmString(QNtlmBuffer &buf, int offset, const QString &s, bool unicode)
static QByteArray qNtlmPhase3(QAuthenticatorPrivate *ctx, const QByteArray &phase2data)
QByteArray qEncodeHmacMd5(QByteArray &key, QByteArrayView message)
#define NTLMSSP_NEGOTIATE_OEM
static const char * methodName(QAuthenticatorPrivate::Method method)
static QByteArray qEncodeNtlmv2Response(const QAuthenticatorPrivate *ctx, const QNtlmPhase2Block &ch, QNtlmPhase3Block *phase3)
static QByteArray digestMd5ResponseHelper(QByteArrayView alg, QByteArrayView userName, QByteArrayView realm, QByteArrayView password, QByteArrayView nonce, QByteArrayView nonceCount, QByteArrayView cNonce, QByteArrayView qop, QByteArrayView method, QByteArrayView digestUri, QByteArrayView hEntity)
static QDataStream & operator>>(QDataStream &s, QNtlmBuffer &b)
static QByteArray qCreatev2Hash(const QAuthenticatorPrivate *ctx, QNtlmPhase3Block *phase3)
static int methodStrength(QAuthenticatorPrivate::Method method)
static void qStreamNtlmBuffer(QDataStream &ds, const QByteArray &s)
#define NTLMSSP_REQUEST_TARGET
static QString qStringFromUcs2Le(QByteArray src)
static void qStreamNtlmString(QDataStream &ds, const QString &s, bool unicode)
#define NTLMSSP_NEGOTIATE_NTLM
static QByteArray qExtractServerTime(const QByteArray &targetInfoBuff)
#define NTLMSSP_NEGOTIATE_UNICODE
static int qEncodeNtlmBuffer(QNtlmBuffer &buf, int offset, const QByteArray &s)
const quint8 hirespversion
#define NTLMSSP_NEGOTIATE_ALWAYS_SIGN
static bool qNtlmDecodePhase2(const QByteArray &data, QNtlmPhase2Block &ch)
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define Q_DECLARE_LOGGING_CATEGORY(name)