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
168
169
170
171
172
173
174
178
179
180QAuthenticator::QAuthenticator()
186
187
188QAuthenticator::~QAuthenticator()
195
196
197QAuthenticator::QAuthenticator(
const QAuthenticator &other)
205
206
207QAuthenticator &QAuthenticator::operator=(
const QAuthenticator &other)
217 d->user = other.d->user;
218 d->userDomain = other.d->userDomain;
219 d->workstation = other.d->workstation;
220 d->extractedUser = other.d->extractedUser;
221 d->password = other.d->password;
222 d->realm = other.d->realm;
223 d->method = other.d->method;
224 d->options = other.d->options;
225 }
else if (d->phase == QAuthenticatorPrivate::Start) {
233
234
235
236bool QAuthenticator::operator==(
const QAuthenticator &other)
const
242 return d->user == other.d->user
243 && d->password == other.d->password
244 && d->realm == other.d->realm
245 && d->method == other.d->method
246 && d->options == other.d->options;
250
251
252
253
254
257
258
259QString QAuthenticator::user()
const
261 return d ? d->user : QString();
265
266
267
268
269void QAuthenticator::setUser(
const QString &user)
271 if (!d || d->user != user) {
274 d->updateCredentials();
279
280
281QString QAuthenticator::password()
const
283 return d ? d->password : QString();
287
288
289
290
291void QAuthenticator::setPassword(
const QString &password)
293 if (!d || d->password != password) {
295 d->password = password;
300
301
302void QAuthenticator::detach()
305 d =
new QAuthenticatorPrivate;
309 if (d->phase == QAuthenticatorPrivate::Done)
310 d->phase = QAuthenticatorPrivate::Start;
314
315
316QString QAuthenticator::realm()
const
318 return d ? d->realm : QString();
322
323
324void QAuthenticator::setRealm(
const QString &realm)
326 if (!d || d->realm != realm) {
333
334
335
336
337
338
339
340
341QVariant QAuthenticator::option(
const QString &opt)
const
343 return d ? d->options.value(opt) : QVariant();
347
348
349
350
351
352
353
354QVariantHash QAuthenticator::options()
const
356 return d ? d->options : QVariantHash();
360
361
362
363
364
365
366
367void QAuthenticator::setOption(
const QString &opt,
const QVariant &value)
369 if (option(opt) != value) {
371 d->options.insert(opt, value);
377
378
379
380
381
382bool QAuthenticator::isNull()
const
388
389
390
391
392
394void QAuthenticator::clear()
397 d =
new QAuthenticatorPrivate;
399 *d = QAuthenticatorPrivate();
401 d->phase = QAuthenticatorPrivate::Done;
405class QSSPIWindowsHandles
408 CredHandle credHandle;
409 CtxtHandle ctxHandle;
411#elif QT_CONFIG(gssapi)
415 Q_DISABLE_COPY_MOVE(QGssApiHandles)
416 QGssApiHandles() =
default;
419 OM_uint32 ignored = 0;
421 gss_release_name(&ignored, &targetName);
423 gss_delete_sec_context(&ignored, &gssCtx, GSS_C_NO_BUFFER);
426 gss_ctx_id_t gssCtx =
nullptr;
427 gss_name_t targetName =
nullptr;
432QAuthenticatorPrivate::QAuthenticatorPrivate()
438 cnonce = QCryptographicHash::hash(QByteArray::number(QRandomGenerator::system()->generate64(), 16),
439 QCryptographicHash::Md5).toHex();
443QAuthenticatorPrivate::~QAuthenticatorPrivate() =
default;
445void QAuthenticatorPrivate::updateCredentials()
447 int separatorPosn = 0;
450 case QAuthenticatorPrivate::Ntlm:
451 if ((separatorPosn = user.indexOf(
"\\"_L1)) != -1) {
454 userDomain = user.left(separatorPosn);
455 extractedUser = user.mid(separatorPosn + 1);
457 extractedUser = user;
468bool QAuthenticatorPrivate::isMethodSupported(QByteArrayView method)
470 Q_ASSERT(!method.startsWith(
' '));
471 auto separator = method.indexOf(
' ');
473 method = method.first(separator);
474 const auto isSupported = [method](QByteArrayView reference) {
475 return method.compare(reference, Qt::CaseInsensitive) == 0;
477 static const char methods[][10] = {
481#if QT_CONFIG(sspi) || QT_CONFIG(gssapi)
485 return std::any_of(methods, methods + std::size(methods), isSupported);
490 auto opts = QAuthenticatorPrivate::parseDigestAuthenticationChallenge(value);
491 if (
auto it = opts.constFind(
"algorithm"); it != opts.cend()) {
497 auto view = QByteArrayView(alg).first(3);
498 return view.compare(
"MD5", Qt::CaseInsensitive) == 0;
504
505
506
507
508
509
510
511
516 case QAuthenticatorPrivate::None:
return 0;
517 case QAuthenticatorPrivate::Basic:
return 1;
518 case QAuthenticatorPrivate::DigestMd5:
return 2;
519 case QAuthenticatorPrivate::Ntlm:
return 3;
520 case QAuthenticatorPrivate::Negotiate:
return 4;
523 Q_UNREACHABLE_RETURN(0);
526static const char *
methodName(QAuthenticatorPrivate::Method method)
529 case QAuthenticatorPrivate::None:
return "None";
530 case QAuthenticatorPrivate::Basic:
return "Basic";
531 case QAuthenticatorPrivate::DigestMd5:
return "Digest-MD5";
532 case QAuthenticatorPrivate::Ntlm:
return "NTLM";
533 case QAuthenticatorPrivate::Negotiate:
return "Negotiate";
536 Q_UNREACHABLE_RETURN(
"Unknown");
540void QAuthenticatorPrivate::parseHttpResponse(
const QHttpHeaders &headers,
543 const auto search = isProxy ? QHttpHeaders::WellKnownHeader::ProxyAuthenticate
544 : QHttpHeaders::WellKnownHeader::WWWAuthenticate;
546 const Method previousMethod = method;
547 const Phase previousPhase = phase;
550
551
552
553
554
555
556
557
559 QByteArrayView headerVal;
560 const QByteArrayList values = headers.values(search);
561 for (
const auto ¤t : values) {
562 const QLatin1StringView str(current);
563 if (methodStrength(method) < methodStrength(Basic)
564 && str.startsWith(
"basic"_L1, Qt::CaseInsensitive)) {
566 headerVal = QByteArrayView(current).mid(6);
567 }
else if (methodStrength(method) < methodStrength(Ntlm)
568 && str.startsWith(
"ntlm"_L1, Qt::CaseInsensitive)) {
570 headerVal = QByteArrayView(current).mid(5);
571 }
else if (methodStrength(method) < methodStrength(DigestMd5)
572 && str.startsWith(
"digest"_L1, Qt::CaseInsensitive)) {
574 if (!verifyDigestMD5(QByteArrayView(current).sliced(7)))
578 headerVal = QByteArrayView(current).mid(7);
579 }
else if (methodStrength(method) < methodStrength(Negotiate)
580 && str.startsWith(
"negotiate"_L1, Qt::CaseInsensitive)) {
581#if QT_CONFIG(sspi) || QT_CONFIG(gssapi)
583 headerVal = QByteArrayView(current).mid(10);
590 if (previousPhase == Phase2
591 && methodStrength(method) < methodStrength(previousMethod)) {
593 "Authentication method downgrade from %s to %s refused "
594 "during multi-round exchange (possible man-in-the-middle). "
595 "Aborting authentication.",
596 methodName(previousMethod), methodName(method));
600 challenge = QByteArray();
606 challenge = headerVal.trimmed().toByteArray();
607 QHash<QByteArray, QByteArray> options = parseDigestAuthenticationChallenge(challenge);
611 auto privSetRealm = [
this](QString newRealm) {
612 if (newRealm != realm) {
615 realm = std::move(newRealm);
616 this->options[
"realm"_L1] = realm;
622 privSetRealm(QString::fromLatin1(options.value(
"realm")));
623 if (user.isEmpty() && password.isEmpty())
631 privSetRealm(QString::fromLatin1(options.value(
"realm")));
632 if (options.value(
"stale").compare(
"true", Qt::CaseInsensitive) == 0) {
636 if (user.isEmpty() && password.isEmpty())
642 challenge = QByteArray();
647QByteArray QAuthenticatorPrivate::calculateResponse(QByteArrayView requestMethod,
648 QByteArrayView path, QStringView host)
650#if !QT_CONFIG(sspi) && !QT_CONFIG(gssapi)
654 QByteArrayView methodString;
656 case QAuthenticatorPrivate::None:
659 case QAuthenticatorPrivate::Basic:
660 methodString =
"Basic";
661 response = (user +
':'_L1 + password).toLatin1().toBase64();
664 case QAuthenticatorPrivate::DigestMd5:
665 methodString =
"Digest";
666 response = digestMd5Response(challenge, requestMethod, path);
669 case QAuthenticatorPrivate::Ntlm:
670 methodString =
"NTLM";
671 if (challenge.isEmpty()) {
673 QByteArray phase1Token;
674 if (user.isEmpty()) {
675 phase1Token = qSspiStartup(
this, method, host);
676 }
else if (!q_SSPI_library_load()) {
678 qWarning(
"Failed to load the SSPI libraries");
681 if (!phase1Token.isEmpty()) {
682 response = phase1Token.toBase64();
687 response = qNtlmPhase1().toBase64();
695 QByteArray phase3Token;
696 if (sspiWindowsHandles)
697 phase3Token = qSspiContinue(
this, method, host, QByteArray::fromBase64(challenge));
698 if (!phase3Token.isEmpty()) {
699 response = phase3Token.toBase64();
704 response = qNtlmPhase3(
this, QByteArray::fromBase64(challenge)).toBase64();
711 case QAuthenticatorPrivate::Negotiate:
712 methodString =
"Negotiate";
713 if (challenge.isEmpty()) {
714 QByteArray phase1Token;
716 phase1Token = qSspiStartup(
this, method, host);
717#elif QT_CONFIG(gssapi)
718 phase1Token = qGssapiStartup(
this, host);
721 if (!phase1Token.isEmpty()) {
722 response = phase1Token.toBase64();
729 QByteArray phase3Token;
731 if (sspiWindowsHandles)
732 phase3Token = qSspiContinue(
this, method, host, QByteArray::fromBase64(challenge));
733#elif QT_CONFIG(gssapi)
735 phase3Token = qGssapiContinue(
this, QByteArray::fromBase64(challenge));
737 if (!phase3Token.isEmpty()) {
738 response = phase3Token.toBase64();
750 return methodString +
' ' + response;
758 for (
auto element : QLatin1StringView(data).tokenize(
','_L1)) {
759 if (element ==
"auth"_L1)
765QHash<QByteArray, QByteArray>
766QAuthenticatorPrivate::parseDigestAuthenticationChallenge(QByteArrayView challenge)
768 QHash<QByteArray, QByteArray> options;
770 const char *d = challenge.data();
771 const char *end = d + challenge.size();
773 while (d < end && (*d ==
' ' || *d ==
'\n' || *d ==
'\r'))
775 const char *start = d;
776 while (d < end && *d !=
'=')
780 QByteArrayView key = QByteArrayView(start, d - start);
784 bool quote = (*d ==
'"');
791 bool backslash =
false;
792 if (*d ==
'\\' && d < end - 1) {
808 while (d < end && *d !=
',')
812 options[key.toByteArray()] = std::move(value);
815 QByteArray qop = options.value(
"qop");
816 if (!qop.isEmpty()) {
817 if (!containsAuth(qop))
818 return QHash<QByteArray, QByteArray>();
826 options[
"qop"] =
"auth";
833
834
835
836
837
838
844 QByteArrayView userName,
845 QByteArrayView realm,
846 QByteArrayView password,
847 QByteArrayView nonce,
848 QByteArrayView nonceCount,
849 QByteArrayView cNonce,
851 QByteArrayView method,
852 QByteArrayView digestUri,
853 QByteArrayView hEntity
857 hash.addData(userName);
861 hash.addData(password);
863 if (alg.compare(
"md5-sess", Qt::CaseInsensitive) == 0) {
869 hash.addData(ha1.toHex());
873 hash.addData(cNonce);
880 hash.addData(method);
882 hash.addData(digestUri);
883 if (qop.compare(
"auth-int", Qt::CaseInsensitive) == 0) {
885 hash.addData(hEntity);
896 hash.addData(nonceCount);
898 hash.addData(cNonce);
903 hash.addData(ha2hex);
904 return hash.result().toHex();
907QByteArray QAuthenticatorPrivate::digestMd5Response(QByteArrayView challenge, QByteArrayView method,
910 QHash<QByteArray,QByteArray> options = parseDigestAuthenticationChallenge(challenge);
913 QByteArray nonceCountString = QByteArray::number(nonceCount, 16);
914 while (nonceCountString.size() < 8)
915 nonceCountString.prepend(
'0');
917 QByteArray nonce = options.value(
"nonce");
918 QByteArray opaque = options.value(
"opaque");
919 QByteArray qop = options.value(
"qop");
922 QByteArray response = digestMd5ResponseHelper(options.value(
"algorithm"), user.toLatin1(),
923 realm.toLatin1(), password.toLatin1(),
924 nonce, nonceCountString,
929 QByteArray credentials;
930 credentials +=
"username=\"" + user.toLatin1() +
"\", ";
931 credentials +=
"realm=\"" + realm.toLatin1() +
"\", ";
932 credentials +=
"nonce=\"" + nonce +
"\", ";
933 credentials +=
"uri=\"" + path +
"\", ";
934 if (!opaque.isEmpty())
935 credentials +=
"opaque=\"" + opaque +
"\", ";
936 credentials +=
"response=\"" + response +
'"';
937 if (!options.value(
"algorithm").isEmpty())
938 credentials +=
", algorithm=" + options.value(
"algorithm");
939 if (!options.value(
"qop").isEmpty()) {
940 credentials +=
", qop=" + qop +
", ";
941 credentials +=
"nc=" + nonceCountString +
", ";
942 credentials +=
"cnonce=\"" + cnonce +
'"';
954
955
956
957
958
959
962
963
964
965#define NTLMSSP_NEGOTIATE_UNICODE 0x00000001
968
969
970#define NTLMSSP_NEGOTIATE_OEM 0x00000002
973
974
975
976#define NTLMSSP_REQUEST_TARGET 0x00000004
979
980
981
982#define NTLMSSP_NEGOTIATE_SIGN 0x00000010
985
986
987
988#define NTLMSSP_NEGOTIATE_SEAL 0x00000020
991
992
993#define NTLMSSP_NEGOTIATE_DATAGRAM 0x00000040
996
997
998
999#define NTLMSSP_NEGOTIATE_LM_KEY 0x00000080
1002
1003
1004#define NTLMSSP_NEGOTIATE_NTLM 0x00000200
1007
1008
1009
1010
1011
1012#define NTLMSSP_NEGOTIATE_DOMAIN_SUPPLIED 0x00001000
1015
1016
1017
1018
1019#define NTLMSSP_NEGOTIATE_WORKSTATION_SUPPLIED 0x00002000
1022
1023
1024
1025
1026#define NTLMSSP_NEGOTIATE_LOCAL_CALL 0x00004000
1029
1030
1031
1032#define NTLMSSP_NEGOTIATE_ALWAYS_SIGN 0x00008000
1035
1036
1037
1038#define NTLMSSP_TARGET_TYPE_DOMAIN 0x00010000
1041
1042
1043
1044#define NTLMSSP_TARGET_TYPE_SERVER 0x00020000
1047
1048
1049
1050
1051#define NTLMSSP_TARGET_TYPE_SHARE 0x00040000
1054
1055
1056
1057
1058
1059#define NTLMSSP_NEGOTIATE_NTLM2 0x00080000
1062
1063
1064
1065
1066#define NTLMSSP_NEGOTIATE_TARGET_INFO 0x00800000
1069
1070
1071#define NTLMSSP_NEGOTIATE_128 0x20000000
1074
1075
1076
1077
1078
1079#define NTLMSSP_NEGOTIATE_KEY_EXCHANGE 0x40000000
1082
1083
1084#define NTLMSSP_NEGOTIATE_56 0x80000000
1087
1088
1089#define AVTIMESTAMP 7
1101
1102
1103
1104
1105
1106
1107
1108
1109
1122 ds.writeRawData(s.constData(), s.size());
1129 qStreamNtlmBuffer(ds, s.toLatin1());
1134 ds << quint16(ch.unicode());
1142 buf.maxLen = buf.len;
1143 buf.offset = (offset + 1) & ~1;
1144 return buf.offset + buf.len;
1151 return qEncodeNtlmBuffer(buf, offset, s.toLatin1());
1152 buf.len = 2 * s.size();
1153 buf.maxLen = buf.len;
1154 buf.offset = (offset + 1) & ~1;
1155 return buf.offset + buf.len;
1161 s << b.len << b.maxLen << b.offset;
1167 s >> b.len >> b.maxLen >> b.offset;
1175 char magic[8] = {
'N',
'T',
'L',
'M',
'S',
'S',
'P',
'\0'};
1208 char magic[8] = {
'N',
'T',
'L',
'M',
'S',
'S',
'P',
'\0'};
1234 if (!b.domainStr.isEmpty())
1235 qStreamNtlmString(s, b.domainStr, unicode);
1236 if (!b.workstationStr.isEmpty())
1237 qStreamNtlmString(s, b.workstationStr, unicode);
1247 s << b.ntlmResponse;
1254 if (!b.domainStr.isEmpty())
1255 qStreamNtlmString(s, b.domainStr, unicode);
1257 qStreamNtlmString(s, b.userStr, unicode);
1259 if (!b.workstationStr.isEmpty())
1260 qStreamNtlmString(s, b.workstationStr, unicode);
1263 qStreamNtlmBuffer(s, b.lmResponseBuf);
1264 qStreamNtlmBuffer(s, b.ntlmResponseBuf);
1274 QDataStream ds(&rc, QIODevice::WriteOnly);
1275 ds.setByteOrder(QDataStream::LittleEndian);
1285 unsigned short *d = (
unsigned short*)rc.data();
1286 for (QChar ch : src)
1287 *d++ = qToLittleEndian(quint16(ch.unicode()));
1295 Q_ASSERT(src.size() % 2 == 0);
1296 unsigned short *d = (
unsigned short*)src.data();
1297 for (
int i = 0; i < src.size() / 2; ++i) {
1298 d[i] = qFromLittleEndian(d[i]);
1300 return QString((
const QChar *)src.data(), src.size()/2);
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1328 Q_ASSERT_X(!(message.isEmpty()),
"qEncodeHmacMd5",
"Empty message check");
1329 Q_ASSERT_X(!(key.isEmpty()),
"qEncodeHmacMd5",
"Empty key check");
1341 key = hash.result();
1346 key = key.leftJustified(
blockSize,0,
true);
1351 for(
int i = 0; i<key.size();i++) {
1352 iKeyPad[i] = key[i]^iKeyPad[i];
1356 for(
int i = 0; i<key.size();i++) {
1357 oKeyPad[i] = key[i]^oKeyPad[i];
1360 iKeyPad.append(message);
1363 hash.addData(iKeyPad);
1364 QByteArrayView hMsg = hash.resultView();
1368 oKeyPad.append(hMsg);
1370 hash.addData(oKeyPad);
1371 hmacDigest = hash.result();
1375
1376
1377
1378
1381
1388 Q_ASSERT(phase3 !=
nullptr);
1391 if (phase3->v2Hash.size() == 0) {
1393 QByteArray passUnicode = qStringAsUcs2Le(ctx->password);
1394 md4.addData(passUnicode);
1397 Q_ASSERT(hashKey.size() == 16);
1400 qStringAsUcs2Le(ctx->extractedUser.toUpper()) +
1401 qStringAsUcs2Le(phase3->domainStr);
1403 phase3->v2Hash = qEncodeHmacMd5(hashKey, message);
1405 return phase3->v2Hash;
1410 Q_ASSERT(ctx->cnonce.size() >= 8);
1419 const char *ptr = targetInfoBuff.constBegin();
1420 const char *end = targetInfoBuff.constEnd();
1424 while (end - ptr >= 4) {
1425 avId = qFromLittleEndian<quint16>(ptr + 0);
1426 avLen = qFromLittleEndian<quint16>(ptr + 2);
1430 if (avLen != NtlmFileTimeSize)
1435 timeArray.assign(ptr, ptr + NtlmFileTimeSize);
1439 if (avLen > end - ptr)
1450 Q_ASSERT(phase3 !=
nullptr);
1455 QDataStream ds(&temp, QIODevice::WriteOnly);
1456 ds.setByteOrder(QDataStream::LittleEndian);
1459 ds << hirespversion;
1463 ds.writeRawData(reserved1.constData(), reserved1.size());
1468 if (ch.targetInfo.len)
1470 timeArray = qExtractServerTime(ch.targetInfoBuff);
1474 if (timeArray.size()) {
1475 ds.writeRawData(timeArray.constData(), timeArray.size());
1480 time = QDateTime::currentSecsSinceEpoch() + 11644473600;
1483 time = time * Q_UINT64_C(10000000);
1489 ds.writeRawData(clientCh.constData(), clientCh.size());
1493 ds.writeRawData(reserved2.constData(), reserved2.size());
1495 if (ch.targetInfo.len > 0) {
1496 ds.writeRawData(ch.targetInfoBuff.constData(),
1497 ch.targetInfoBuff.size());
1502 ds.writeRawData(reserved3.constData(), reserved3.size());
1505 message.append(temp);
1507 QByteArray ntChallengeResp = qEncodeHmacMd5(phase3->v2Hash, message);
1508 ntChallengeResp.append(temp);
1510 return ntChallengeResp;
1517 Q_ASSERT(phase3 !=
nullptr);
1524 message.append(clientCh);
1526 QByteArray lmChallengeResp = qEncodeHmacMd5(phase3->v2Hash, message);
1527 lmChallengeResp.append(clientCh);
1529 return lmChallengeResp;
1538 QDataStream ds(data);
1539 ds.setByteOrder(QDataStream::LittleEndian);
1540 if (ds.readRawData(ch
.magic, 8) < 8)
1542 if (strncmp(ch
.magic,
"NTLMSSP", 8) != 0)
1549 ds >> ch.targetName;
1551 if (ds.readRawData((
char *)ch
.challenge, 8) < 8)
1553 ds >> ch.context[0] >> ch.context[1];
1554 ds >> ch.targetInfo;
1556 if (ch.targetName.len > 0) {
1558 if (qAddOverflow(qsizetype(ch.targetName.offset), qsizetype(ch.targetName.len), &total))
1560 if (total > data.size())
1563 ch.targetNameStr = qStringFromUcs2Le(data.mid(ch.targetName.offset, ch.targetName.len));
1566 if (ch.targetInfo.len > 0) {
1568 if (qAddOverflow(qsizetype(ch.targetInfo.offset), qsizetype(ch.targetInfo.len), &total))
1570 if (total > data.size())
1573 ch.targetInfoBuff = data.mid(ch.targetInfo.offset, ch.targetInfo.len);
1587 QDataStream ds(&rc, QIODevice::WriteOnly);
1588 ds.setByteOrder(QDataStream::LittleEndian);
1610 if (ctx->userDomain.isEmpty() && !ctx->extractedUser.contains(u'@')) {
1611 offset = qEncodeNtlmString(pb.domain, offset, ch.targetNameStr, unicode);
1612 pb.domainStr = ch.targetNameStr;
1614 offset = qEncodeNtlmString(pb.domain, offset, ctx->userDomain, unicode);
1615 pb.domainStr = ctx->userDomain;
1618 offset = qEncodeNtlmString(pb.user, offset, ctx->extractedUser, unicode);
1619 pb.userStr = ctx->extractedUser;
1621 offset = qEncodeNtlmString(pb.workstation, offset, ctx->workstation, unicode);
1622 pb.workstationStr = ctx->workstation;
1625 if (ch.targetInfo.len > 0) {
1630 offset = qEncodeNtlmBuffer(pb.lmResponse, offset, pb.lmResponseBuf);
1634 offset = qEncodeNtlmBuffer(pb.ntlmResponse, offset, pb.ntlmResponseBuf);
1651static PSecurityFunctionTableW pSecurityFunctionTable =
nullptr;
1653static bool q_SSPI_library_load()
1655 Q_CONSTINIT
static QBasicMutex mutex;
1656 QMutexLocker l(&mutex);
1658 if (pSecurityFunctionTable ==
nullptr)
1659 pSecurityFunctionTable = InitSecurityInterfaceW();
1661 if (pSecurityFunctionTable ==
nullptr)
1667static QByteArray qSspiStartup(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method,
1670 if (!q_SSPI_library_load())
1671 return QByteArray();
1675 if (!ctx->sspiWindowsHandles)
1676 ctx->sspiWindowsHandles.reset(
new QSSPIWindowsHandles);
1677 SecInvalidateHandle(&ctx->sspiWindowsHandles->credHandle);
1678 SecInvalidateHandle(&ctx->sspiWindowsHandles->ctxHandle);
1680 SEC_WINNT_AUTH_IDENTITY auth;
1681 auth.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
1682 bool useAuth =
false;
1683 if (method == QAuthenticatorPrivate::Negotiate && !ctx->user.isEmpty()) {
1684 auth.Domain =
const_cast<ushort *>(
reinterpret_cast<
const ushort *>(ctx->userDomain.constData()));
1685 auth.DomainLength = ctx->userDomain.size();
1686 auth.User =
const_cast<ushort *>(
reinterpret_cast<
const ushort *>(ctx->user.constData()));
1687 auth.UserLength = ctx->user.size();
1688 auth.Password =
const_cast<ushort *>(
reinterpret_cast<
const ushort *>(ctx->password.constData()));
1689 auth.PasswordLength = ctx->password.size();
1694 SECURITY_STATUS secStatus = pSecurityFunctionTable->AcquireCredentialsHandle(
1696 (SEC_WCHAR *)(method == QAuthenticatorPrivate::Negotiate ? L"Negotiate" : L"NTLM"),
1697 SECPKG_CRED_OUTBOUND,
nullptr, useAuth ? &auth :
nullptr,
nullptr,
nullptr,
1698 &ctx->sspiWindowsHandles->credHandle, &expiry
1700 if (secStatus != SEC_E_OK) {
1701 ctx->sspiWindowsHandles.reset(
nullptr);
1702 return QByteArray();
1705 return qSspiContinue(ctx, method, host);
1708static QByteArray qSspiContinue(QAuthenticatorPrivate *ctx, QAuthenticatorPrivate::Method method,
1709 QStringView host, QByteArrayView challenge)
1712 SecBuffer challengeBuf;
1713 SecBuffer responseBuf;
1714 SecBufferDesc challengeDesc;
1715 SecBufferDesc responseDesc;
1716 unsigned long attrs;
1719 if (!challenge.isEmpty())
1722 challengeDesc.ulVersion = SECBUFFER_VERSION;
1723 challengeDesc.cBuffers = 1;
1724 challengeDesc.pBuffers = &challengeBuf;
1725 challengeBuf.BufferType = SECBUFFER_TOKEN;
1726 challengeBuf.pvBuffer = (PVOID)(challenge.data());
1727 challengeBuf.cbBuffer = challenge.length();
1731 responseDesc.ulVersion = SECBUFFER_VERSION;
1732 responseDesc.cBuffers = 1;
1733 responseDesc.pBuffers = &responseBuf;
1734 responseBuf.BufferType = SECBUFFER_TOKEN;
1735 responseBuf.pvBuffer =
nullptr;
1736 responseBuf.cbBuffer = 0;
1739 QString targetName = ctx->options.value(
"spn"_L1).toString();
1740 if (targetName.isEmpty())
1741 targetName =
"HTTP/"_L1 + host;
1742 const std::wstring targetNameW = (method == QAuthenticatorPrivate::Negotiate
1743 ? targetName : QString()).toStdWString();
1746 SECURITY_STATUS secStatus = pSecurityFunctionTable->InitializeSecurityContext(
1747 &ctx->sspiWindowsHandles->credHandle,
1748 !challenge.isEmpty() ? &ctx->sspiWindowsHandles->ctxHandle :
nullptr,
1749 const_cast<
wchar_t*>(targetNameW.data()),
1750 ISC_REQ_ALLOCATE_MEMORY,
1751 0, SECURITY_NATIVE_DREP,
1752 !challenge.isEmpty() ? &challengeDesc :
nullptr,
1753 0, &ctx->sspiWindowsHandles->ctxHandle,
1754 &responseDesc, &attrs,
1758 if (secStatus == SEC_I_COMPLETE_NEEDED || secStatus == SEC_I_COMPLETE_AND_CONTINUE) {
1759 secStatus = pSecurityFunctionTable->CompleteAuthToken(&ctx->sspiWindowsHandles->ctxHandle,
1763 if (secStatus != SEC_I_COMPLETE_AND_CONTINUE && secStatus != SEC_I_CONTINUE_NEEDED) {
1764 pSecurityFunctionTable->FreeCredentialsHandle(&ctx->sspiWindowsHandles->credHandle);
1765 pSecurityFunctionTable->DeleteSecurityContext(&ctx->sspiWindowsHandles->ctxHandle);
1766 ctx->sspiWindowsHandles.reset(
nullptr);
1769 result = QByteArray((
const char*)responseBuf.pvBuffer, responseBuf.cbBuffer);
1770 pSecurityFunctionTable->FreeContextBuffer(responseBuf.pvBuffer);
1777#elif QT_CONFIG(gssapi)
1783static void q_GSSAPI_error_int(
const char *message, OM_uint32 stat,
int type)
1785 OM_uint32 minStat, msgCtx = 0;
1786 gss_buffer_desc msg;
1789 gss_display_status(&minStat, stat, type, GSS_C_NO_OID, &msgCtx, &msg);
1790 qCDebug(lcAuthenticator) << message <<
": " <<
reinterpret_cast<
const char*>(msg.value);
1791 gss_release_buffer(&minStat, &msg);
1796static void q_GSSAPI_error(
const char *message, OM_uint32 majStat, OM_uint32 minStat)
1799 q_GSSAPI_error_int(message, majStat, GSS_C_GSS_CODE);
1802 q_GSSAPI_error_int(message, minStat, GSS_C_MECH_CODE);
1805static gss_name_t qGSsapiGetServiceName(QStringView host)
1807 QByteArray serviceName =
"HTTPS@" + host.toLocal8Bit();
1808 gss_buffer_desc nameDesc = {
static_cast<std::size_t>(serviceName.size()), serviceName.data()};
1810 gss_name_t importedName;
1812 OM_uint32 majStat = gss_import_name(&minStat, &nameDesc,
1813 GSS_C_NT_HOSTBASED_SERVICE, &importedName);
1815 if (majStat != GSS_S_COMPLETE) {
1816 q_GSSAPI_error(
"gss_import_name error", majStat, minStat);
1819 return importedName;
1823static QByteArray qGssapiStartup(QAuthenticatorPrivate *ctx, QStringView host)
1825 if (!ctx->gssApiHandles)
1826 ctx->gssApiHandles.reset(
new QGssApiHandles);
1829 gss_name_t name = qGSsapiGetServiceName(host);
1830 if (name ==
nullptr) {
1831 ctx->gssApiHandles.reset(
nullptr);
1832 return QByteArray();
1834 ctx->gssApiHandles->targetName = name;
1837 ctx->gssApiHandles->gssCtx = GSS_C_NO_CONTEXT;
1838 return qGssapiContinue(ctx);
1841static gss_cred_id_t qGssapiCreateCredentials(
const QByteArray &realm,
1842 const QByteArray &username,
1843 const QByteArray &password)
1847 QByteArray qualified = username;
1848 if (!realm.isEmpty())
1849 qualified +=
'@' + realm;
1850 gss_buffer_desc nameBuffer {
1851 size_t(qualified.size()),
1854 gss_name_t importedName;
1855 OM_uint32 majStat = gss_import_name(&minStat, &nameBuffer, GSS_C_NT_USER_NAME, &importedName);
1856 if (majStat != GSS_S_COMPLETE) {
1857 q_GSSAPI_error(
"gss_import_name error", majStat, minStat);
1861 gss_buffer_desc passwordBuffer {
1862 size_t(password.size()),
1863 const_cast<
char *>(password.data())
1865 gss_cred_id_t credHandle;
1866 majStat = gss_acquire_cred_with_password(&minStat, importedName, &passwordBuffer,
1867 GSS_C_INDEFINITE, GSS_C_NO_OID_SET,
1868 GSS_C_INITIATE, &credHandle,
1872 gss_release_name(&ignored, &importedName);
1874 if (majStat != GSS_S_COMPLETE) {
1875 q_GSSAPI_error(
"gss_acquire_cred_with_password error", majStat, minStat);
1882static QByteArray qGssapiContinue(QAuthenticatorPrivate *ctx, QByteArrayView challenge)
1884 OM_uint32 majStat, minStat, ignored;
1886 gss_buffer_desc inBuf = {0,
nullptr};
1887 gss_buffer_desc outBuf;
1889 if (!challenge.isEmpty()) {
1890 inBuf.value =
const_cast<
char*>(challenge.data());
1891 inBuf.length = challenge.size();
1894 gss_cred_id_t credHandle = GSS_C_NO_CREDENTIAL;
1895 if (!ctx->user.isEmpty()) {
1896 credHandle = qGssapiCreateCredentials(ctx->userDomain.toLocal8Bit(),
1897 ctx->user.toLocal8Bit(),
1898 ctx->password.toLocal8Bit());
1899 if (credHandle ==
nullptr) {
1900 ctx->gssApiHandles.reset(
nullptr);
1905 majStat = gss_init_sec_context(&minStat,
1907 &ctx->gssApiHandles->gssCtx,
1908 ctx->gssApiHandles->targetName,
1912 GSS_C_NO_CHANNEL_BINDINGS,
1913 challenge.isEmpty() ? GSS_C_NO_BUFFER : &inBuf,
1918 gss_release_cred(&ignored, &credHandle);
1920 if (outBuf.length != 0)
1921 result = QByteArray(
reinterpret_cast<
const char*>(outBuf.value), outBuf.length);
1922 gss_release_buffer(&ignored, &outBuf);
1924 if (majStat != GSS_S_CONTINUE_NEEDED) {
1925 if (majStat != GSS_S_COMPLETE)
1926 q_GSSAPI_error(
"gss_init_sec_context error", majStat, minStat);
1927 ctx->gssApiHandles.reset(
nullptr);
1939#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)