| 1 | // |
| 2 | // NTLMCredentials.cpp |
| 3 | // |
| 4 | // Library: Net |
| 5 | // Package: NTLM |
| 6 | // Module: NTLMCredentials |
| 7 | // |
| 8 | // Copyright (c) 2019, Applied Informatics Software Engineering GmbH. |
| 9 | // and Contributors. |
| 10 | // |
| 11 | // SPDX-License-Identifier: BSL-1.0 |
| 12 | // |
| 13 | |
| 14 | |
| 15 | #include "Poco/Net/NTLMCredentials.h" |
| 16 | #include "Poco/HMACEngine.h" |
| 17 | #include "Poco/MD4Engine.h" |
| 18 | #include "Poco/MD5Engine.h" |
| 19 | #include "Poco/DigestStream.h" |
| 20 | #include "Poco/StreamCopier.h" |
| 21 | #include "Poco/UTF8Encoding.h" |
| 22 | #include "Poco/UTF16Encoding.h" |
| 23 | #include "Poco/TextConverter.h" |
| 24 | #include "Poco/UTF8String.h" |
| 25 | #include "Poco/Random.h" |
| 26 | #include "Poco/Timestamp.h" |
| 27 | #include "Poco/MemoryStream.h" |
| 28 | #include "Poco/Base64Encoder.h" |
| 29 | #include "Poco/Base64Decoder.h" |
| 30 | #include <sstream> |
| 31 | #include <cstring> |
| 32 | |
| 33 | |
| 34 | namespace Poco { |
| 35 | namespace Net { |
| 36 | |
| 37 | |
| 38 | const std::string NTLMCredentials::NTLMSSP("NTLMSSP" ); |
| 39 | |
| 40 | |
| 41 | std::vector<unsigned char> NTLMCredentials::createNonce() |
| 42 | { |
| 43 | Poco::MD5Engine md5; |
| 44 | Poco::Random rnd; |
| 45 | rnd.seed(); |
| 46 | |
| 47 | Poco::UInt32 n = rnd.next(); |
| 48 | md5.update(&n, sizeof(n)); |
| 49 | |
| 50 | Poco::Timestamp ts; |
| 51 | md5.update(&ts, sizeof(ts)); |
| 52 | |
| 53 | Poco::DigestEngine::Digest d = md5.digest(); |
| 54 | d.resize(8); |
| 55 | |
| 56 | return d; |
| 57 | } |
| 58 | |
| 59 | |
| 60 | Poco::UInt64 NTLMCredentials::createTimestamp() |
| 61 | { |
| 62 | const Poco::UInt64 EPOCH_DELTA_SECONDS = 11644473600; // seconds between January 1, 1970 and January 1, 1601 |
| 63 | |
| 64 | Poco::Timestamp now; |
| 65 | Poco::UInt64 ts = now.epochMicroseconds(); |
| 66 | ts += EPOCH_DELTA_SECONDS*1000000; // since January 1, 1601 |
| 67 | ts *= 10; // tenths of a microsecond |
| 68 | return ts; |
| 69 | } |
| 70 | |
| 71 | |
| 72 | std::vector<unsigned char> NTLMCredentials::createPasswordHash(const std::string& password) |
| 73 | { |
| 74 | Poco::UTF8Encoding utf8; |
| 75 | Poco::UTF16Encoding utf16(Poco::UTF16Encoding::LITTLE_ENDIAN_BYTE_ORDER); |
| 76 | Poco::TextConverter converter(utf8, utf16); |
| 77 | |
| 78 | std::string utf16Password; |
| 79 | converter.convert(password, utf16Password); |
| 80 | |
| 81 | Poco::MD4Engine md4; |
| 82 | md4.update(utf16Password); |
| 83 | return md4.digest(); |
| 84 | } |
| 85 | |
| 86 | |
| 87 | std::vector<unsigned char> NTLMCredentials::createNTLMv2Hash(const std::string& username, const std::string& target, const std::string& password) |
| 88 | { |
| 89 | Poco::UTF8Encoding utf8; |
| 90 | Poco::UTF16Encoding utf16(Poco::UTF16Encoding::LITTLE_ENDIAN_BYTE_ORDER); |
| 91 | Poco::TextConverter converter(utf8, utf16); |
| 92 | |
| 93 | std::vector<unsigned char> passwordHash = createPasswordHash(password); |
| 94 | |
| 95 | std::string userDomain = Poco::UTF8::toUpper(username); |
| 96 | userDomain += target; |
| 97 | |
| 98 | std::string utf16UserDomain; |
| 99 | converter.convert(userDomain, utf16UserDomain); |
| 100 | |
| 101 | std::string passwordHashString(reinterpret_cast<const char*>(&passwordHash[0]), passwordHash.size()); |
| 102 | Poco::HMACEngine<Poco::MD5Engine> hmac(passwordHashString); |
| 103 | hmac.update(utf16UserDomain); |
| 104 | return hmac.digest(); |
| 105 | } |
| 106 | |
| 107 | |
| 108 | std::vector<unsigned char> NTLMCredentials::createLMv2Response(const std::vector<unsigned char>& ntlm2Hash, const std::vector<unsigned char>& challenge, const std::vector<unsigned char>& nonce) |
| 109 | { |
| 110 | poco_assert (challenge.size() == 8); |
| 111 | poco_assert (nonce.size() == 8); |
| 112 | |
| 113 | std::vector<unsigned char> lm2Response; |
| 114 | |
| 115 | std::string ntlm2HashString(reinterpret_cast<const char*>(&ntlm2Hash[0]), ntlm2Hash.size()); |
| 116 | Poco::HMACEngine<Poco::MD5Engine> hmac2(ntlm2HashString); |
| 117 | hmac2.update(&challenge[0], challenge.size()); |
| 118 | hmac2.update(&nonce[0], nonce.size()); |
| 119 | lm2Response = hmac2.digest(); |
| 120 | lm2Response.insert(lm2Response.end(), nonce.begin(), nonce.end()); |
| 121 | |
| 122 | return lm2Response; |
| 123 | } |
| 124 | |
| 125 | |
| 126 | std::vector<unsigned char> NTLMCredentials::createNTLMv2Response(const std::vector<unsigned char>& ntlm2Hash, const std::vector<unsigned char>& challenge, const std::vector<unsigned char>& nonce, const std::vector<unsigned char>& targetInfo, Poco::UInt64 timestamp) |
| 127 | { |
| 128 | poco_assert (challenge.size() == 8); |
| 129 | poco_assert (nonce.size() == 8); |
| 130 | |
| 131 | std::vector<unsigned char> blob; |
| 132 | blob.resize(28 + targetInfo.size() + 4 + 16); |
| 133 | |
| 134 | Poco::MemoryOutputStream blobStream(reinterpret_cast<char*>(&blob[16]), blob.size() - 16); |
| 135 | Poco::BinaryWriter writer(blobStream, Poco::BinaryWriter::LITTLE_ENDIAN_BYTE_ORDER); |
| 136 | writer << Poco::UInt32(0x0101); |
| 137 | writer << Poco::UInt32(0); |
| 138 | writer << timestamp; |
| 139 | writer.writeRaw(reinterpret_cast<const char*>(&nonce[0]), nonce.size()); |
| 140 | writer << Poco::UInt32(0); |
| 141 | if (targetInfo.size() > 0) |
| 142 | { |
| 143 | writer.writeRaw(reinterpret_cast<const char*>(&targetInfo[0]), targetInfo.size()); |
| 144 | } |
| 145 | writer << Poco::UInt32(0); |
| 146 | |
| 147 | poco_assert_dbg (blobStream.charsWritten() == blob.size() - 16); |
| 148 | |
| 149 | std::string ntlm2HashString(reinterpret_cast<const char*>(&ntlm2Hash[0]), ntlm2Hash.size()); |
| 150 | Poco::HMACEngine<Poco::MD5Engine> hmac2(ntlm2HashString); |
| 151 | hmac2.update(&challenge[0], challenge.size()); |
| 152 | hmac2.update(&blob[16], blob.size() - 16); |
| 153 | Poco::DigestEngine::Digest d = hmac2.digest(); |
| 154 | |
| 155 | poco_assert_dbg (d.size() == 16); |
| 156 | |
| 157 | std::memcpy(&blob[0], &d[0], 16); |
| 158 | |
| 159 | return blob; |
| 160 | } |
| 161 | |
| 162 | |
| 163 | std::vector<unsigned char> NTLMCredentials::formatNegotiateMessage(const NegotiateMessage& message) |
| 164 | { |
| 165 | Poco::UTF8Encoding utf8; |
| 166 | Poco::UTF16Encoding utf16(Poco::UTF16Encoding::LITTLE_ENDIAN_BYTE_ORDER); |
| 167 | Poco::TextConverter converter(utf8, utf16); |
| 168 | |
| 169 | std::string utf16Domain; |
| 170 | converter.convert(message.domain, utf16Domain); |
| 171 | |
| 172 | std::string utf16Workstation; |
| 173 | converter.convert(message.workstation, utf16Workstation); |
| 174 | |
| 175 | std::size_t size = 8 // signature |
| 176 | + 4 // type |
| 177 | + 4 // flags |
| 178 | + 8 + utf16Domain.size() |
| 179 | + 8 + utf16Workstation.size(); |
| 180 | |
| 181 | Poco::UInt32 flags = message.flags | NTLM_FLAG_NEGOTIATE_UNICODE | NTLM_FLAG_REQUEST_TARGET | NTLM_FLAG_NEGOTIATE_NTLM | NTLM_FLAG_NEGOTIATE_NTLM2_KEY | NTLM_FLAG_NEGOTIATE_ALWAYS_SIGN; |
| 182 | if (!utf16Domain.empty()) flags |= NTLM_FLAG_DOMAIN_SUPPLIED; |
| 183 | if (!utf16Workstation.empty()) flags |= NTLM_FLAG_WORKST_SUPPLIED; |
| 184 | |
| 185 | BufferDesc domainDesc(static_cast<Poco::UInt16>(utf16Domain.size()), 8 + 4 + 4 + 8); |
| 186 | BufferDesc workstDesc(static_cast<Poco::UInt16>(utf16Workstation.size()), domainDesc.offset + domainDesc.length); |
| 187 | |
| 188 | std::vector<unsigned char> buffer(size); |
| 189 | Poco::MemoryOutputStream bufferStream(reinterpret_cast<char*>(&buffer[0]), buffer.size()); |
| 190 | Poco::BinaryWriter writer(bufferStream, Poco::BinaryWriter::LITTLE_ENDIAN_BYTE_ORDER); |
| 191 | writer.writeRaw(NTLMSSP.c_str(), 8); |
| 192 | writer << Poco::UInt32(NTLM_MESSAGE_TYPE_NEGOTIATE); |
| 193 | writer << flags; |
| 194 | writeBufferDesc(writer, domainDesc); |
| 195 | writeBufferDesc(writer, workstDesc); |
| 196 | writer.writeRaw(utf16Domain); |
| 197 | writer.writeRaw(utf16Workstation); |
| 198 | |
| 199 | return buffer; |
| 200 | } |
| 201 | |
| 202 | |
| 203 | bool NTLMCredentials::parseChallengeMessage(const unsigned char* buffer, std::size_t size, ChallengeMessage& message) |
| 204 | { |
| 205 | Poco::MemoryInputStream istr(reinterpret_cast<const char*>(buffer), size); |
| 206 | Poco::BinaryReader reader(istr, Poco::BinaryReader::LITTLE_ENDIAN_BYTE_ORDER); |
| 207 | |
| 208 | std::string signature; |
| 209 | reader.readRaw(7, signature); |
| 210 | if (signature != NTLMSSP) return false; |
| 211 | |
| 212 | Poco::UInt8 zero; |
| 213 | reader >> zero; |
| 214 | if (zero != 0) return false; |
| 215 | |
| 216 | Poco::UInt32 type; |
| 217 | reader >> type; |
| 218 | if (type != NTLM_MESSAGE_TYPE_CHALLENGE) return false; |
| 219 | |
| 220 | BufferDesc targetDesc; |
| 221 | readBufferDesc(reader, targetDesc); |
| 222 | if (targetDesc.offset + targetDesc.length > size) return false; |
| 223 | |
| 224 | reader >> message.flags; |
| 225 | |
| 226 | message.challenge.resize(8); |
| 227 | reader.readRaw(reinterpret_cast<char*>(&message.challenge[0]), 8); |
| 228 | |
| 229 | if (message.flags & NTLM_FLAG_NEGOTIATE_TARGET) |
| 230 | { |
| 231 | Poco::UInt64 reserved; |
| 232 | reader >> reserved; |
| 233 | } |
| 234 | |
| 235 | BufferDesc targetInfoDesc; |
| 236 | if (message.flags & NTLM_FLAG_NEGOTIATE_TARGET) |
| 237 | { |
| 238 | readBufferDesc(reader, targetInfoDesc); |
| 239 | if (targetInfoDesc.offset + targetInfoDesc.length > size) return false; |
| 240 | } |
| 241 | |
| 242 | if (targetDesc.length > 0) |
| 243 | { |
| 244 | if (message.flags & NTLM_FLAG_NEGOTIATE_UNICODE) |
| 245 | { |
| 246 | Poco::UTF16Encoding utf16(Poco::UTF16Encoding::LITTLE_ENDIAN_BYTE_ORDER); |
| 247 | Poco::UTF8Encoding utf8; |
| 248 | Poco::TextConverter converter(utf16, utf8); |
| 249 | converter.convert(buffer + targetDesc.offset, targetDesc.length, message.target); |
| 250 | if (targetDesc.reserved == 0) message.target.resize(std::strlen(message.target.c_str())); |
| 251 | } |
| 252 | else |
| 253 | { |
| 254 | message.target.assign(buffer + targetDesc.offset, buffer + targetDesc.offset + targetDesc.length); |
| 255 | } |
| 256 | } |
| 257 | |
| 258 | if (targetInfoDesc.length > 0) |
| 259 | { |
| 260 | message.targetInfo.assign(buffer + targetInfoDesc.offset, buffer + targetInfoDesc.offset + targetInfoDesc.length); |
| 261 | } |
| 262 | |
| 263 | return true; |
| 264 | } |
| 265 | |
| 266 | |
| 267 | std::vector<unsigned char> NTLMCredentials::formatAuthenticateMessage(const AuthenticateMessage& message) |
| 268 | { |
| 269 | Poco::UTF8Encoding utf8; |
| 270 | Poco::UTF16Encoding utf16(Poco::UTF16Encoding::LITTLE_ENDIAN_BYTE_ORDER); |
| 271 | Poco::TextConverter converter(utf8, utf16); |
| 272 | |
| 273 | std::string utf16Target; |
| 274 | converter.convert(message.target, utf16Target); |
| 275 | |
| 276 | std::string utf16Username; |
| 277 | converter.convert(message.username, utf16Username); |
| 278 | |
| 279 | std::string utf16Workstation; |
| 280 | converter.convert(message.workstation, utf16Workstation); |
| 281 | |
| 282 | std::size_t size = 8 // signature |
| 283 | + 4 // type |
| 284 | + 8 + message.lmResponse.size() |
| 285 | + 8 + message.ntlmResponse.size() |
| 286 | + 8 + utf16Target.size() |
| 287 | + 8 + utf16Username.size() |
| 288 | + 8 + utf16Workstation.size() |
| 289 | + 8 // session key |
| 290 | + 4; // flags |
| 291 | |
| 292 | Poco::UInt32 flags = message.flags | NTLM_FLAG_NEGOTIATE_UNICODE; |
| 293 | |
| 294 | BufferDesc lmDesc(static_cast<Poco::UInt16>(message.lmResponse.size()), 64); |
| 295 | BufferDesc ntlmDesc(static_cast<Poco::UInt16>(message.ntlmResponse.size()), lmDesc.offset + lmDesc.length); |
| 296 | BufferDesc targetDesc(static_cast<Poco::UInt16>(utf16Target.size()), ntlmDesc.offset + ntlmDesc.length); |
| 297 | BufferDesc usernameDesc(static_cast<Poco::UInt16>(utf16Username.size()), targetDesc.offset + targetDesc.length); |
| 298 | BufferDesc workstDesc(static_cast<Poco::UInt16>(utf16Workstation.size()), usernameDesc.offset + usernameDesc.length); |
| 299 | BufferDesc sessionKeyDesc(0, workstDesc.offset + workstDesc.length); |
| 300 | |
| 301 | std::vector<unsigned char> buffer(size); |
| 302 | Poco::MemoryOutputStream bufferStream(reinterpret_cast<char*>(&buffer[0]), buffer.size()); |
| 303 | Poco::BinaryWriter writer(bufferStream, Poco::BinaryWriter::LITTLE_ENDIAN_BYTE_ORDER); |
| 304 | writer.writeRaw(NTLMSSP.c_str(), 8); |
| 305 | writer << Poco::UInt32(NTLM_MESSAGE_TYPE_AUTHENTICATE); |
| 306 | writeBufferDesc(writer, lmDesc); |
| 307 | writeBufferDesc(writer, ntlmDesc); |
| 308 | writeBufferDesc(writer, targetDesc); |
| 309 | writeBufferDesc(writer, usernameDesc); |
| 310 | writeBufferDesc(writer, workstDesc); |
| 311 | writeBufferDesc(writer, sessionKeyDesc); |
| 312 | writer << flags; |
| 313 | writer.writeRaw(reinterpret_cast<const char*>(&message.lmResponse[0]), message.lmResponse.size()); |
| 314 | writer.writeRaw(reinterpret_cast<const char*>(&message.ntlmResponse[0]), message.ntlmResponse.size()); |
| 315 | writer.writeRaw(utf16Target); |
| 316 | writer.writeRaw(utf16Username); |
| 317 | writer.writeRaw(utf16Workstation); |
| 318 | |
| 319 | return buffer; |
| 320 | } |
| 321 | |
| 322 | |
| 323 | void NTLMCredentials::readBufferDesc(Poco::BinaryReader& reader, BufferDesc& desc) |
| 324 | { |
| 325 | reader >> desc.length >> desc.reserved >> desc.offset; |
| 326 | } |
| 327 | |
| 328 | |
| 329 | void NTLMCredentials::writeBufferDesc(Poco::BinaryWriter& writer, const BufferDesc& desc) |
| 330 | { |
| 331 | writer << desc.length << desc.reserved << desc.offset; |
| 332 | } |
| 333 | |
| 334 | |
| 335 | void NTLMCredentials::(const std::string& usernameAndDomain, std::string& username, std::string& domain) |
| 336 | { |
| 337 | std::string::size_type pos = usernameAndDomain.find('\\'); |
| 338 | if (pos != std::string::npos) |
| 339 | { |
| 340 | domain.assign(usernameAndDomain, 0, pos); |
| 341 | username.assign(usernameAndDomain, pos + 1, std::string::npos); |
| 342 | return; |
| 343 | } |
| 344 | else |
| 345 | { |
| 346 | pos = usernameAndDomain.find('@'); |
| 347 | if (pos != std::string::npos) |
| 348 | { |
| 349 | username.assign(usernameAndDomain, 0, pos); |
| 350 | domain.assign(usernameAndDomain, pos + 1, std::string::npos); |
| 351 | return; |
| 352 | } |
| 353 | } |
| 354 | username = usernameAndDomain; |
| 355 | } |
| 356 | |
| 357 | |
| 358 | std::string NTLMCredentials::toBase64(const std::vector<unsigned char>& buffer) |
| 359 | { |
| 360 | std::ostringstream ostr; |
| 361 | Poco::Base64Encoder base64(ostr); |
| 362 | base64.rdbuf()->setLineLength(0); |
| 363 | base64.write(reinterpret_cast<const char*>(&buffer[0]), buffer.size()); |
| 364 | base64.close(); |
| 365 | return ostr.str(); |
| 366 | } |
| 367 | |
| 368 | |
| 369 | std::vector<unsigned char> NTLMCredentials::fromBase64(const std::string& base64) |
| 370 | { |
| 371 | Poco::MemoryInputStream istr(base64.data(), base64.size()); |
| 372 | Poco::Base64Decoder debase64(istr); |
| 373 | std::vector<unsigned char> buffer(base64.size()); |
| 374 | debase64.read(reinterpret_cast<char*>(&buffer[0]), buffer.size()); |
| 375 | buffer.resize(static_cast<std::size_t>(debase64.gcount())); |
| 376 | return buffer; |
| 377 | } |
| 378 | |
| 379 | |
| 380 | } } // namespace Poco::Net |
| 381 | |