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
34namespace Poco {
35namespace Net {
36
37
38const std::string NTLMCredentials::NTLMSSP("NTLMSSP");
39
40
41std::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
60Poco::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
72std::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
87std::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
108std::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
126std::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
163std::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
203bool 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
267std::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
323void NTLMCredentials::readBufferDesc(Poco::BinaryReader& reader, BufferDesc& desc)
324{
325 reader >> desc.length >> desc.reserved >> desc.offset;
326}
327
328
329void NTLMCredentials::writeBufferDesc(Poco::BinaryWriter& writer, const BufferDesc& desc)
330{
331 writer << desc.length << desc.reserved << desc.offset;
332}
333
334
335void NTLMCredentials::splitUsername(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
358std::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
369std::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