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 | |