File indexing completed on 2024-04-21 15:00:43
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2004 Szombathelyi György <gyurco@freemail.hu> 0004 0005 The implementation is based on the documentation and sample code 0006 at https://davenport.sourceforge.net/ntlm.html 0007 The DES encryption functions are from libntlm 0008 at https://gitlab.com/gsasl/libntlm/ 0009 0010 SPDX-License-Identifier: LGPL-2.0-only 0011 */ 0012 0013 #include "kntlm.h" 0014 #include "des.h" 0015 0016 #include <cstring> 0017 0018 #include <QCryptographicHash> 0019 #include <QDate> 0020 #include <QRandomGenerator> 0021 #include <QtEndian> 0022 0023 static const char NTLM_SIGNATURE[] = "NTLMSSP"; 0024 0025 static constexpr int NTLM_BLOB_SIZE = 28; 0026 0027 static QByteArray QString2UnicodeLE(const QString &target) 0028 { 0029 QByteArray unicode(target.length() * 2, 0); 0030 0031 for (int i = 0; i < target.length(); i++) { 0032 ((quint16 *)unicode.data())[i] = qToLittleEndian(target[i].unicode()); 0033 } 0034 0035 return unicode; 0036 } 0037 0038 static QString UnicodeLE2QString(const QChar *data, uint len) 0039 { 0040 QString ret; 0041 0042 for (uint i = 0; i < len; i++) { 0043 ret += qFromLittleEndian(data[i].unicode()); 0044 } 0045 0046 return ret; 0047 } 0048 0049 static QByteArray getBuf(const QByteArray &buf, const KNTLM::SecBuf &secbuf) 0050 { 0051 quint32 offset = qFromLittleEndian(secbuf.offset); 0052 quint16 len = qFromLittleEndian(secbuf.len); 0053 0054 // watch for buffer overflows 0055 if (offset > (quint32)buf.size() || offset + len > (quint32)buf.size()) { 0056 return QByteArray(); 0057 } 0058 0059 return QByteArray(buf.data() + offset, len); 0060 } 0061 0062 static void addBuf(QByteArray &buf, KNTLM::SecBuf &secbuf, const QByteArray &data) 0063 { 0064 quint32 offset = (buf.size() + 1) & 0xfffffffe; 0065 quint16 len = data.size(); 0066 quint16 maxlen = data.size(); 0067 0068 secbuf.offset = qToLittleEndian((quint32)offset); 0069 secbuf.len = qToLittleEndian(len); 0070 secbuf.maxlen = qToLittleEndian(maxlen); 0071 buf.resize(offset + len); 0072 memcpy(buf.data() + offset, data.data(), data.size()); 0073 } 0074 0075 static QString getString(const QByteArray &buf, const KNTLM::SecBuf &secbuf, bool unicode) 0076 { 0077 // watch for buffer overflows 0078 quint32 offset = qFromLittleEndian((quint32)secbuf.offset); 0079 quint16 len = qFromLittleEndian(secbuf.len); 0080 0081 if (offset > (quint32)buf.size() || offset + len > (quint32)buf.size()) { 0082 return QString(); 0083 } 0084 0085 const char *c = buf.data() + offset; 0086 0087 if (unicode) { 0088 return UnicodeLE2QString((QChar *)c, len >> 1); 0089 } 0090 0091 return QString::fromLatin1(c, len); 0092 } 0093 0094 static void addString(QByteArray &buf, KNTLM::SecBuf &secbuf, const QString &str, bool unicode = false) 0095 { 0096 if (unicode) { 0097 addBuf(buf, secbuf, QString2UnicodeLE(str)); 0098 return; 0099 } 0100 0101 addBuf(buf, secbuf, str.toLatin1()); 0102 } 0103 0104 /* 0105 * turns a 56 bit key into the 64 bit, odd parity key and sets the key. 0106 * The key schedule ks is also set. 0107 */ 0108 static void convertKey(unsigned char *key_56, void *ks) 0109 { 0110 unsigned char key[8]; 0111 0112 key[0] = key_56[0]; 0113 key[1] = ((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1); 0114 key[2] = ((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2); 0115 key[3] = ((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3); 0116 key[4] = ((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4); 0117 key[5] = ((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5); 0118 key[6] = ((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6); 0119 key[7] = (key_56[6] << 1) & 0xFF; 0120 0121 for (unsigned char &b : key) { 0122 const bool needsParity = ((((b >> 7) ^ (b >> 6) ^ (b >> 5) ^ (b >> 4) ^ (b >> 3) ^ (b >> 2) ^ (b >> 1)) & 0x01) == 0); 0123 0124 if (needsParity) { 0125 b |= 0x01; 0126 } else { 0127 b &= 0xfe; 0128 } 0129 } 0130 0131 ntlm_des_set_key((DES_KEY *)ks, (char *)&key, sizeof(key)); 0132 memset(&key, 0, sizeof(key)); 0133 } 0134 0135 static QByteArray createBlob(const QByteArray &targetinfo) 0136 { 0137 QByteArray blob(NTLM_BLOB_SIZE + 4 + targetinfo.size(), 0); 0138 0139 KNTLM::Blob *bl = (KNTLM::Blob *)blob.data(); 0140 bl->signature = qToBigEndian((quint32)0x01010000); 0141 quint64 now = QDateTime::currentSecsSinceEpoch(); 0142 now += (quint64)3600 * (quint64)24 * (quint64)134774; 0143 now *= (quint64)10000000; 0144 bl->timestamp = qToLittleEndian(now); 0145 0146 for (unsigned char &b : bl->challenge) { 0147 b = QRandomGenerator::global()->bounded(0xff); 0148 } 0149 0150 memcpy(blob.data() + NTLM_BLOB_SIZE, targetinfo.data(), targetinfo.size()); 0151 return blob; 0152 } 0153 0154 static QByteArray hmacMD5(const QByteArray &data, const QByteArray &key) 0155 { 0156 QByteArray ipad(64, 0x36); 0157 QByteArray opad(64, 0x5c); 0158 0159 Q_ASSERT(key.size() <= 64); 0160 0161 for (int i = qMin(key.size(), 64) - 1; i >= 0; i--) { 0162 ipad.data()[i] ^= key[i]; 0163 opad.data()[i] ^= key[i]; 0164 } 0165 0166 QByteArray content(ipad + data); 0167 0168 QCryptographicHash md5(QCryptographicHash::Md5); 0169 md5.addData(content); 0170 content = opad + md5.result(); 0171 0172 md5.reset(); 0173 md5.addData(content); 0174 0175 return md5.result(); 0176 } 0177 0178 /*************************************** KNTLM implementation ***************************************/ 0179 0180 bool KNTLM::getNegotiate(QByteArray &negotiate, const QString &domain, const QString &workstation, quint32 flags) 0181 { 0182 QByteArray rbuf(sizeof(Negotiate), 0); 0183 0184 memcpy(rbuf.data(), NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE)); 0185 ((Negotiate *)rbuf.data())->msgType = qToLittleEndian((quint32)1); 0186 0187 if (!domain.isEmpty()) { 0188 flags |= Negotiate_Domain_Supplied; 0189 addString(rbuf, ((Negotiate *)rbuf.data())->domain, domain); 0190 } 0191 0192 if (!workstation.isEmpty()) { 0193 flags |= Negotiate_WS_Supplied; 0194 addString(rbuf, ((Negotiate *)rbuf.data())->workstation, workstation); 0195 } 0196 0197 ((Negotiate *)rbuf.data())->flags = qToLittleEndian(flags); 0198 negotiate = rbuf; 0199 return true; 0200 } 0201 0202 bool KNTLM::getAuth(QByteArray &auth, 0203 const QByteArray &challenge, 0204 const QString &user, 0205 const QString &password, 0206 const QString &domain, 0207 const QString &workstation, 0208 AuthFlags authflags) 0209 { 0210 QByteArray rbuf(sizeof(Auth), 0); 0211 Challenge *ch = (Challenge *)challenge.data(); 0212 QByteArray response; 0213 const uint chsize = challenge.size(); 0214 bool unicode = false; 0215 QString dom; 0216 0217 // challenge structure too small 0218 if (chsize < 32) { 0219 return false; 0220 } 0221 0222 unicode = qFromLittleEndian(ch->flags) & Negotiate_Unicode; 0223 0224 // If the domain is NULL (i.e. QString()) use the target domain. If the domain is empty 0225 // (i.e. QString("")) use an empty domain. 0226 if (domain.isNull()) { 0227 dom = getString(challenge, ch->targetName, unicode); 0228 } else { 0229 dom = domain; 0230 } 0231 0232 memcpy(rbuf.data(), NTLM_SIGNATURE, sizeof(NTLM_SIGNATURE)); 0233 ((Auth *)rbuf.data())->msgType = qToLittleEndian((quint32)3); 0234 ((Auth *)rbuf.data())->flags = ch->flags; 0235 QByteArray targetInfo; 0236 if (chsize >= sizeof(Challenge)) { 0237 targetInfo = getBuf(challenge, ch->targetInfo); 0238 } 0239 0240 if (!(authflags & Force_V1) 0241 && ((authflags & Force_V2) // 0242 || (!targetInfo.isEmpty() && (qFromLittleEndian(ch->flags) & Negotiate_Target_Info))) /* may support NTLMv2 */) { 0243 bool ret = false; 0244 0245 if (qFromLittleEndian(ch->flags) & Negotiate_NTLM) { 0246 if (targetInfo.isEmpty()) { 0247 return false; 0248 } 0249 0250 response = getNTLMv2Response(dom, user, password, targetInfo, ch->challengeData); 0251 addBuf(rbuf, ((Auth *)rbuf.data())->ntResponse, response); 0252 ret = true; 0253 } 0254 0255 if (authflags & Add_LM) { 0256 response = getLMv2Response(dom, user, password, ch->challengeData); 0257 addBuf(rbuf, ((Auth *)rbuf.data())->lmResponse, response); 0258 ret = true; 0259 } 0260 0261 if (!ret) { 0262 return false; 0263 } 0264 } else { // if no targetinfo structure and NTLMv2 or LMv2 not forced, or v1 forced, try the older methods 0265 bool ret = false; 0266 0267 if (qFromLittleEndian(ch->flags) & Negotiate_NTLM) { 0268 response = getNTLMResponse(password, ch->challengeData); 0269 addBuf(rbuf, ((Auth *)rbuf.data())->ntResponse, response); 0270 ret = true; 0271 } 0272 0273 if (authflags & Add_LM) { 0274 response = getLMResponse(password, ch->challengeData); 0275 addBuf(rbuf, ((Auth *)rbuf.data())->lmResponse, response); 0276 ret = true; 0277 } 0278 0279 if (!ret) { 0280 return false; 0281 } 0282 } 0283 0284 if (!dom.isEmpty()) { 0285 addString(rbuf, ((Auth *)rbuf.data())->domain, dom, unicode); 0286 } 0287 0288 addString(rbuf, ((Auth *)rbuf.data())->user, user, unicode); 0289 0290 if (!workstation.isEmpty()) { 0291 addString(rbuf, ((Auth *)rbuf.data())->workstation, workstation, unicode); 0292 } 0293 0294 auth = rbuf; 0295 return true; 0296 } 0297 0298 QByteArray KNTLM::getLMResponse(const QString &password, const unsigned char *challenge) 0299 { 0300 QByteArray hash; 0301 QByteArray answer; 0302 0303 hash = lmHash(password); 0304 hash.resize(21); 0305 memset(hash.data() + 16, 0, 5); 0306 answer = lmResponse(hash, challenge); 0307 hash.fill(0); 0308 return answer; 0309 } 0310 0311 QByteArray KNTLM::lmHash(const QString &password) 0312 { 0313 QByteArray keyBytes(14, 0); 0314 QByteArray hash(16, 0); 0315 DES_KEY ks; 0316 const char *magic = "KGS!@#$%"; 0317 0318 strncpy(keyBytes.data(), password.toUpper().toLocal8Bit().constData(), 14); 0319 0320 convertKey((unsigned char *)keyBytes.data(), &ks); 0321 ntlm_des_ecb_encrypt(magic, 8, &ks, (unsigned char *)hash.data()); 0322 0323 convertKey((unsigned char *)keyBytes.data() + 7, &ks); 0324 ntlm_des_ecb_encrypt(magic, 8, &ks, (unsigned char *)hash.data() + 8); 0325 0326 keyBytes.fill(0); 0327 memset(&ks, 0, sizeof(ks)); 0328 0329 return hash; 0330 } 0331 0332 QByteArray KNTLM::lmResponse(const QByteArray &hash, const unsigned char *challenge) 0333 { 0334 DES_KEY ks; 0335 QByteArray answer(24, 0); 0336 0337 convertKey((unsigned char *)hash.data(), &ks); 0338 ntlm_des_ecb_encrypt(challenge, 8, &ks, (unsigned char *)answer.data()); 0339 0340 convertKey((unsigned char *)hash.data() + 7, &ks); 0341 ntlm_des_ecb_encrypt(challenge, 8, &ks, (unsigned char *)answer.data() + 8); 0342 0343 convertKey((unsigned char *)hash.data() + 14, &ks); 0344 ntlm_des_ecb_encrypt(challenge, 8, &ks, (unsigned char *)answer.data() + 16); 0345 0346 memset(&ks, 0, sizeof(ks)); 0347 return answer; 0348 } 0349 0350 QByteArray KNTLM::getNTLMResponse(const QString &password, const unsigned char *challenge) 0351 { 0352 QByteArray hash = ntlmHash(password); 0353 hash.resize(21); 0354 memset(hash.data() + 16, 0, 5); 0355 QByteArray answer = lmResponse(hash, challenge); 0356 hash.fill(0); 0357 return answer; 0358 } 0359 0360 QByteArray KNTLM::ntlmHash(const QString &password) 0361 { 0362 QByteArray unicode; 0363 unicode = QString2UnicodeLE(password); 0364 0365 return QCryptographicHash::hash(unicode, QCryptographicHash::Md4); 0366 } 0367 0368 QByteArray KNTLM::getNTLMv2Response(const QString &target, 0369 const QString &user, 0370 const QString &password, 0371 const QByteArray &targetInformation, 0372 const unsigned char *challenge) 0373 { 0374 QByteArray hash = ntlmv2Hash(target, user, password); 0375 QByteArray blob = createBlob(targetInformation); 0376 return lmv2Response(hash, blob, challenge); 0377 } 0378 0379 QByteArray KNTLM::getLMv2Response(const QString &target, const QString &user, const QString &password, const unsigned char *challenge) 0380 { 0381 QByteArray hash = ntlmv2Hash(target, user, password); 0382 QByteArray clientChallenge(8, 0); 0383 0384 for (uint i = 0; i < 8; i++) { 0385 clientChallenge.data()[i] = QRandomGenerator::global()->bounded(0xff); 0386 } 0387 0388 return lmv2Response(hash, clientChallenge, challenge); 0389 } 0390 0391 QByteArray KNTLM::ntlmv2Hash(const QString &target, const QString &user, const QString &password) 0392 { 0393 const QByteArray hash = ntlmHash(password); 0394 const QByteArray key = QString2UnicodeLE(user.toUpper() + target); 0395 return hmacMD5(key, hash); 0396 } 0397 0398 QByteArray KNTLM::lmv2Response(const QByteArray &hash, const QByteArray &clientData, const unsigned char *challenge) 0399 { 0400 QByteArray data(8 + clientData.size(), 0); 0401 memcpy(data.data(), challenge, 8); 0402 memcpy(data.data() + 8, clientData.data(), clientData.size()); 0403 0404 QByteArray mac = hmacMD5(data, hash); 0405 mac.resize(16 + clientData.size()); 0406 memcpy(mac.data() + 16, clientData.data(), clientData.size()); 0407 return mac; 0408 }