1 // Copyright 2017 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/ntlm/ntlm_client.h"
6
7 #include <string>
8
9 #include "base/containers/span.h"
10 #include "base/strings/string_util.h"
11 #include "build/build_config.h"
12 #include "net/ntlm/ntlm.h"
13 #include "net/ntlm/ntlm_buffer_reader.h"
14 #include "net/ntlm/ntlm_buffer_writer.h"
15 #include "net/ntlm/ntlm_test_data.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17
18 namespace net::ntlm {
19
20 namespace {
21
GenerateAuthMsg(const NtlmClient & client,base::span<const uint8_t> challenge_msg)22 std::vector<uint8_t> GenerateAuthMsg(const NtlmClient& client,
23 base::span<const uint8_t> challenge_msg) {
24 return client.GenerateAuthenticateMessage(
25 test::kNtlmDomain, test::kUser, test::kPassword, test::kHostnameAscii,
26 reinterpret_cast<const char*>(test::kChannelBindings), test::kNtlmSpn,
27 test::kClientTimestamp, test::kClientChallenge, challenge_msg);
28 }
29
GenerateAuthMsg(const NtlmClient & client,const NtlmBufferWriter & challenge_writer)30 std::vector<uint8_t> GenerateAuthMsg(const NtlmClient& client,
31 const NtlmBufferWriter& challenge_writer) {
32 return GenerateAuthMsg(client, challenge_writer.GetBuffer());
33 }
34
GetAuthMsgResult(const NtlmClient & client,const NtlmBufferWriter & challenge_writer)35 bool GetAuthMsgResult(const NtlmClient& client,
36 const NtlmBufferWriter& challenge_writer) {
37 return !GenerateAuthMsg(client, challenge_writer).empty();
38 }
39
ReadBytesPayload(NtlmBufferReader * reader,base::span<uint8_t> buffer)40 bool ReadBytesPayload(NtlmBufferReader* reader, base::span<uint8_t> buffer) {
41 SecurityBuffer sec_buf;
42 return reader->ReadSecurityBuffer(&sec_buf) &&
43 (sec_buf.length == buffer.size()) &&
44 reader->ReadBytesFrom(sec_buf, buffer);
45 }
46
47 // Reads bytes from a payload and assigns them to a string. This makes
48 // no assumptions about the underlying encoding.
ReadStringPayload(NtlmBufferReader * reader,std::string * str)49 bool ReadStringPayload(NtlmBufferReader* reader, std::string* str) {
50 SecurityBuffer sec_buf;
51 if (!reader->ReadSecurityBuffer(&sec_buf))
52 return false;
53
54 str->resize(sec_buf.length);
55 if (!reader->ReadBytesFrom(sec_buf, base::as_writable_byte_span(*str))) {
56 return false;
57 }
58
59 return true;
60 }
61
62 // Reads bytes from a payload and assigns them to a string16. This makes
63 // no assumptions about the underlying encoding. This will fail if there
64 // are an odd number of bytes in the payload.
ReadString16Payload(NtlmBufferReader * reader,std::u16string * str)65 bool ReadString16Payload(NtlmBufferReader* reader, std::u16string* str) {
66 SecurityBuffer sec_buf;
67 if (!reader->ReadSecurityBuffer(&sec_buf) || (sec_buf.length % 2 != 0))
68 return false;
69
70 std::vector<uint8_t> raw(sec_buf.length);
71 if (!reader->ReadBytesFrom(sec_buf, raw))
72 return false;
73
74 #if defined(ARCH_CPU_BIG_ENDIAN)
75 for (size_t i = 0; i < raw.size(); i += 2) {
76 std::swap(raw[i], raw[i + 1]);
77 }
78 #endif
79
80 str->assign(reinterpret_cast<const char16_t*>(raw.data()), raw.size() / 2);
81 return true;
82 }
83
MakeV2ChallengeMessage(size_t target_info_len,std::vector<uint8_t> * out)84 void MakeV2ChallengeMessage(size_t target_info_len, std::vector<uint8_t>* out) {
85 static const size_t kChallengeV2HeaderLen = 56;
86
87 // Leave room for the AV_PAIR header and the EOL pair.
88 size_t server_name_len = target_info_len - kAvPairHeaderLen * 2;
89
90 // See [MS-NLP] Section 2.2.1.2.
91 NtlmBufferWriter challenge(kChallengeV2HeaderLen + target_info_len);
92 ASSERT_TRUE(challenge.WriteMessageHeader(MessageType::kChallenge));
93 ASSERT_TRUE(
94 challenge.WriteSecurityBuffer(SecurityBuffer(0, 0))); // target name
95 ASSERT_TRUE(challenge.WriteFlags(NegotiateFlags::kTargetInfo));
96 ASSERT_TRUE(challenge.WriteZeros(kChallengeLen)); // server challenge
97 ASSERT_TRUE(challenge.WriteZeros(8)); // reserved
98 ASSERT_TRUE(challenge.WriteSecurityBuffer(
99 SecurityBuffer(kChallengeV2HeaderLen, target_info_len))); // target info
100 ASSERT_TRUE(challenge.WriteZeros(8)); // version
101 ASSERT_EQ(kChallengeV2HeaderLen, challenge.GetCursor());
102 ASSERT_TRUE(challenge.WriteAvPair(
103 AvPair(TargetInfoAvId::kServerName,
104 std::vector<uint8_t>(server_name_len, 'a'))));
105 ASSERT_TRUE(challenge.WriteAvPairTerminator());
106 ASSERT_TRUE(challenge.IsEndOfBuffer());
107 *out = challenge.Pass();
108 }
109
110 } // namespace
111
TEST(NtlmClientTest,SimpleConstructionV1)112 TEST(NtlmClientTest, SimpleConstructionV1) {
113 NtlmClient client(NtlmFeatures(false));
114
115 ASSERT_FALSE(client.IsNtlmV2());
116 ASSERT_FALSE(client.IsEpaEnabled());
117 ASSERT_FALSE(client.IsMicEnabled());
118 }
119
TEST(NtlmClientTest,VerifyNegotiateMessageV1)120 TEST(NtlmClientTest, VerifyNegotiateMessageV1) {
121 NtlmClient client(NtlmFeatures(false));
122
123 std::vector<uint8_t> result = client.GetNegotiateMessage();
124
125 ASSERT_EQ(kNegotiateMessageLen, result.size());
126 ASSERT_EQ(0, memcmp(test::kExpectedNegotiateMsg, result.data(),
127 kNegotiateMessageLen));
128 }
129
TEST(NtlmClientTest,MinimalStructurallyValidChallenge)130 TEST(NtlmClientTest, MinimalStructurallyValidChallenge) {
131 NtlmClient client(NtlmFeatures(false));
132
133 NtlmBufferWriter writer(kMinChallengeHeaderLen);
134 ASSERT_TRUE(writer.WriteBytes(base::make_span(test::kMinChallengeMessage)
135 .subspan<0, kMinChallengeHeaderLen>()));
136
137 ASSERT_TRUE(GetAuthMsgResult(client, writer));
138 }
139
TEST(NtlmClientTest,MinimalStructurallyValidChallengeZeroOffset)140 TEST(NtlmClientTest, MinimalStructurallyValidChallengeZeroOffset) {
141 NtlmClient client(NtlmFeatures(false));
142
143 // The spec (2.2.1.2) states that the length SHOULD be 0 and the offset
144 // SHOULD be where the payload would be if it was present. This is the
145 // expected response from a compliant server when no target name is sent.
146 // In reality the offset should always be ignored if the length is zero.
147 // Also implementations often just write zeros.
148 uint8_t raw[kMinChallengeHeaderLen];
149 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen);
150 // Modify the default valid message to overwrite the offset to zero.
151 ASSERT_NE(0x00, raw[16]);
152 raw[16] = 0x00;
153
154 NtlmBufferWriter writer(kMinChallengeHeaderLen);
155 ASSERT_TRUE(writer.WriteBytes(raw));
156
157 ASSERT_TRUE(GetAuthMsgResult(client, writer));
158 }
159
TEST(NtlmClientTest,ChallengeMsgTooShort)160 TEST(NtlmClientTest, ChallengeMsgTooShort) {
161 NtlmClient client(NtlmFeatures(false));
162
163 // Fail because the minimum size valid message is 32 bytes.
164 NtlmBufferWriter writer(kMinChallengeHeaderLen - 1);
165 ASSERT_TRUE(writer.WriteBytes(base::make_span(test::kMinChallengeMessage)
166 .subspan<0, kMinChallengeHeaderLen - 1>()));
167 ASSERT_FALSE(GetAuthMsgResult(client, writer));
168 }
169
TEST(NtlmClientTest,ChallengeMsgNoSig)170 TEST(NtlmClientTest, ChallengeMsgNoSig) {
171 NtlmClient client(NtlmFeatures(false));
172
173 // Fail because the first 8 bytes don't match "NTLMSSP\0"
174 uint8_t raw[kMinChallengeHeaderLen];
175 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen);
176 // Modify the default valid message to overwrite the last byte of the
177 // signature.
178 ASSERT_NE(0xff, raw[7]);
179 raw[7] = 0xff;
180 NtlmBufferWriter writer(kMinChallengeHeaderLen);
181 ASSERT_TRUE(writer.WriteBytes(raw));
182 ASSERT_FALSE(GetAuthMsgResult(client, writer));
183 }
184
TEST(NtlmClientTest,ChallengeMsgWrongMessageType)185 TEST(NtlmClientTest, ChallengeMsgWrongMessageType) {
186 NtlmClient client(NtlmFeatures(false));
187
188 // Fail because the message type should be MessageType::kChallenge
189 // (0x00000002)
190 uint8_t raw[kMinChallengeHeaderLen];
191 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen);
192 // Modify the message type.
193 ASSERT_NE(0x03, raw[8]);
194 raw[8] = 0x03;
195
196 NtlmBufferWriter writer(kMinChallengeHeaderLen);
197 ASSERT_TRUE(writer.WriteBytes(raw));
198
199 ASSERT_FALSE(GetAuthMsgResult(client, writer));
200 }
201
TEST(NtlmClientTest,ChallengeWithNoTargetName)202 TEST(NtlmClientTest, ChallengeWithNoTargetName) {
203 NtlmClient client(NtlmFeatures(false));
204
205 // The spec (2.2.1.2) states that the length SHOULD be 0 and the offset
206 // SHOULD be where the payload would be if it was present. This is the
207 // expected response from a compliant server when no target name is sent.
208 // In reality the offset should always be ignored if the length is zero.
209 // Also implementations often just write zeros.
210 uint8_t raw[kMinChallengeHeaderLen];
211 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen);
212 // Modify the default valid message to overwrite the offset to zero.
213 ASSERT_NE(0x00, raw[16]);
214 raw[16] = 0x00;
215
216 NtlmBufferWriter writer(kMinChallengeHeaderLen);
217 ASSERT_TRUE(writer.WriteBytes(raw));
218
219 ASSERT_TRUE(GetAuthMsgResult(client, writer));
220 }
221
TEST(NtlmClientTest,Type2MessageWithTargetName)222 TEST(NtlmClientTest, Type2MessageWithTargetName) {
223 NtlmClient client(NtlmFeatures(false));
224
225 // One extra byte is provided for target name.
226 uint8_t raw[kMinChallengeHeaderLen + 1];
227 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen);
228 // Put something in the target name.
229 raw[kMinChallengeHeaderLen] = 'Z';
230
231 // Modify the default valid message to indicate 1 byte is present in the
232 // target name payload.
233 ASSERT_NE(0x01, raw[12]);
234 ASSERT_EQ(0x00, raw[13]);
235 ASSERT_NE(0x01, raw[14]);
236 ASSERT_EQ(0x00, raw[15]);
237 raw[12] = 0x01;
238 raw[14] = 0x01;
239
240 NtlmBufferWriter writer(kChallengeHeaderLen + 1);
241 ASSERT_TRUE(writer.WriteBytes(raw));
242 ASSERT_TRUE(GetAuthMsgResult(client, writer));
243 }
244
TEST(NtlmClientTest,NoTargetNameOverflowFromOffset)245 TEST(NtlmClientTest, NoTargetNameOverflowFromOffset) {
246 NtlmClient client(NtlmFeatures(false));
247
248 uint8_t raw[kMinChallengeHeaderLen];
249 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen);
250 // Modify the default valid message to claim that the target name field is 1
251 // byte long overrunning the end of the message message.
252 ASSERT_NE(0x01, raw[12]);
253 ASSERT_EQ(0x00, raw[13]);
254 ASSERT_NE(0x01, raw[14]);
255 ASSERT_EQ(0x00, raw[15]);
256 raw[12] = 0x01;
257 raw[14] = 0x01;
258
259 NtlmBufferWriter writer(kMinChallengeHeaderLen);
260 ASSERT_TRUE(writer.WriteBytes(raw));
261
262 // The above malformed message could cause an implementation to read outside
263 // the message buffer because the offset is past the end of the message.
264 // Verify it gets rejected.
265 ASSERT_FALSE(GetAuthMsgResult(client, writer));
266 }
267
TEST(NtlmClientTest,NoTargetNameOverflowFromLength)268 TEST(NtlmClientTest, NoTargetNameOverflowFromLength) {
269 NtlmClient client(NtlmFeatures(false));
270
271 // Message has 1 extra byte of space after the header for the target name.
272 // One extra byte is provided for target name.
273 uint8_t raw[kMinChallengeHeaderLen + 1];
274 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen);
275 // Put something in the target name.
276 raw[kMinChallengeHeaderLen] = 'Z';
277
278 // Modify the default valid message to indicate 2 bytes are present in the
279 // target name payload (however there is only space for 1).
280 ASSERT_NE(0x02, raw[12]);
281 ASSERT_EQ(0x00, raw[13]);
282 ASSERT_NE(0x02, raw[14]);
283 ASSERT_EQ(0x00, raw[15]);
284 raw[12] = 0x02;
285 raw[14] = 0x02;
286
287 NtlmBufferWriter writer(kMinChallengeHeaderLen + 1);
288 ASSERT_TRUE(writer.WriteBytes(raw));
289
290 // The above malformed message could cause an implementation
291 // to read outside the message buffer because the length is
292 // longer than available space. Verify it gets rejected.
293 ASSERT_FALSE(GetAuthMsgResult(client, writer));
294 }
295
TEST(NtlmClientTest,Type3UnicodeWithSessionSecuritySpecTest)296 TEST(NtlmClientTest, Type3UnicodeWithSessionSecuritySpecTest) {
297 NtlmClient client(NtlmFeatures(false));
298
299 std::vector<uint8_t> result = GenerateAuthMsg(client, test::kChallengeMsgV1);
300
301 ASSERT_FALSE(result.empty());
302 ASSERT_EQ(std::size(test::kExpectedAuthenticateMsgSpecResponseV1),
303 result.size());
304 ASSERT_EQ(0, memcmp(test::kExpectedAuthenticateMsgSpecResponseV1,
305 result.data(), result.size()));
306 }
307
TEST(NtlmClientTest,Type3WithoutUnicode)308 TEST(NtlmClientTest, Type3WithoutUnicode) {
309 NtlmClient client(NtlmFeatures(false));
310
311 std::vector<uint8_t> result = GenerateAuthMsg(
312 client, base::make_span(test::kMinChallengeMessageNoUnicode)
313 .subspan<0, kMinChallengeHeaderLen>());
314 ASSERT_FALSE(result.empty());
315
316 NtlmBufferReader reader(result);
317 ASSERT_TRUE(reader.MatchMessageHeader(MessageType::kAuthenticate));
318
319 // Read the LM and NTLM Response Payloads.
320 uint8_t actual_lm_response[kResponseLenV1];
321 uint8_t actual_ntlm_response[kResponseLenV1];
322
323 ASSERT_TRUE(ReadBytesPayload(&reader, actual_lm_response));
324 ASSERT_TRUE(ReadBytesPayload(&reader, actual_ntlm_response));
325
326 ASSERT_EQ(0, memcmp(test::kExpectedLmResponseWithV1SS, actual_lm_response,
327 kResponseLenV1));
328 ASSERT_EQ(0, memcmp(test::kExpectedNtlmResponseWithV1SS, actual_ntlm_response,
329 kResponseLenV1));
330
331 std::string domain;
332 std::string username;
333 std::string hostname;
334 ASSERT_TRUE(ReadStringPayload(&reader, &domain));
335 ASSERT_EQ(test::kNtlmDomainAscii, domain);
336 ASSERT_TRUE(ReadStringPayload(&reader, &username));
337 ASSERT_EQ(test::kUserAscii, username);
338 ASSERT_TRUE(ReadStringPayload(&reader, &hostname));
339 ASSERT_EQ(test::kHostnameAscii, hostname);
340
341 // The session key is not used in HTTP. Since NTLMSSP_NEGOTIATE_KEY_EXCH
342 // was not sent this is empty.
343 ASSERT_TRUE(reader.MatchEmptySecurityBuffer());
344
345 // Verify the unicode flag is not set and OEM flag is.
346 NegotiateFlags flags;
347 ASSERT_TRUE(reader.ReadFlags(&flags));
348 ASSERT_EQ(NegotiateFlags::kNone, flags & NegotiateFlags::kUnicode);
349 ASSERT_EQ(NegotiateFlags::kOem, flags & NegotiateFlags::kOem);
350 }
351
TEST(NtlmClientTest,ClientDoesNotDowngradeSessionSecurity)352 TEST(NtlmClientTest, ClientDoesNotDowngradeSessionSecurity) {
353 NtlmClient client(NtlmFeatures(false));
354
355 std::vector<uint8_t> result =
356 GenerateAuthMsg(client, base::make_span(test::kMinChallengeMessageNoSS)
357 .subspan<0, kMinChallengeHeaderLen>());
358 ASSERT_FALSE(result.empty());
359
360 NtlmBufferReader reader(result);
361 ASSERT_TRUE(reader.MatchMessageHeader(MessageType::kAuthenticate));
362
363 // Read the LM and NTLM Response Payloads.
364 uint8_t actual_lm_response[kResponseLenV1];
365 uint8_t actual_ntlm_response[kResponseLenV1];
366
367 ASSERT_TRUE(ReadBytesPayload(&reader, actual_lm_response));
368 ASSERT_TRUE(ReadBytesPayload(&reader, actual_ntlm_response));
369
370 // The important part of this test is that even though the
371 // server told the client to drop session security. The client
372 // DID NOT drop it.
373 ASSERT_EQ(0, memcmp(test::kExpectedLmResponseWithV1SS, actual_lm_response,
374 kResponseLenV1));
375 ASSERT_EQ(0, memcmp(test::kExpectedNtlmResponseWithV1SS, actual_ntlm_response,
376 kResponseLenV1));
377
378 std::u16string domain;
379 std::u16string username;
380 std::u16string hostname;
381 ASSERT_TRUE(ReadString16Payload(&reader, &domain));
382 ASSERT_EQ(test::kNtlmDomain, domain);
383 ASSERT_TRUE(ReadString16Payload(&reader, &username));
384 ASSERT_EQ(test::kUser, username);
385 ASSERT_TRUE(ReadString16Payload(&reader, &hostname));
386 ASSERT_EQ(test::kHostname, hostname);
387
388 // The session key is not used in HTTP. Since NTLMSSP_NEGOTIATE_KEY_EXCH
389 // was not sent this is empty.
390 ASSERT_TRUE(reader.MatchEmptySecurityBuffer());
391
392 // Verify the unicode and session security flag is set.
393 NegotiateFlags flags;
394 ASSERT_TRUE(reader.ReadFlags(&flags));
395 ASSERT_EQ(NegotiateFlags::kUnicode, flags & NegotiateFlags::kUnicode);
396 ASSERT_EQ(NegotiateFlags::kExtendedSessionSecurity,
397 flags & NegotiateFlags::kExtendedSessionSecurity);
398 }
399
400 // ------------------------------------------------
401 // NTLM V2 specific tests.
402 // ------------------------------------------------
403
TEST(NtlmClientTest,SimpleConstructionV2)404 TEST(NtlmClientTest, SimpleConstructionV2) {
405 NtlmClient client(NtlmFeatures(true));
406
407 ASSERT_TRUE(client.IsNtlmV2());
408 ASSERT_TRUE(client.IsEpaEnabled());
409 ASSERT_TRUE(client.IsMicEnabled());
410 }
411
TEST(NtlmClientTest,VerifyNegotiateMessageV2)412 TEST(NtlmClientTest, VerifyNegotiateMessageV2) {
413 NtlmClient client(NtlmFeatures(true));
414
415 std::vector<uint8_t> result = client.GetNegotiateMessage();
416 ASSERT_FALSE(result.empty());
417 ASSERT_EQ(std::size(test::kExpectedNegotiateMsg), result.size());
418 ASSERT_EQ(0,
419 memcmp(test::kExpectedNegotiateMsg, result.data(), result.size()));
420 }
421
TEST(NtlmClientTest,VerifyAuthenticateMessageV2)422 TEST(NtlmClientTest, VerifyAuthenticateMessageV2) {
423 // Generate the auth message from the client based on the test challenge
424 // message.
425 NtlmClient client(NtlmFeatures(true));
426 std::vector<uint8_t> result =
427 GenerateAuthMsg(client, test::kChallengeMsgFromSpecV2);
428 ASSERT_FALSE(result.empty());
429 ASSERT_EQ(std::size(test::kExpectedAuthenticateMsgSpecResponseV2),
430 result.size());
431 ASSERT_EQ(0, memcmp(test::kExpectedAuthenticateMsgSpecResponseV2,
432 result.data(), result.size()));
433 }
434
TEST(NtlmClientTest,VerifyAuthenticateMessageInResponseToChallengeWithoutTargetInfoV2)435 TEST(NtlmClientTest,
436 VerifyAuthenticateMessageInResponseToChallengeWithoutTargetInfoV2) {
437 // Test how the V2 client responds when the server sends a challenge that
438 // does not contain target info. eg. Windows 2003 and earlier do not send
439 // this. See [MS-NLMP] Appendix B Item 8. These older Windows servers
440 // support NTLMv2 but don't send target info. Other implementations may
441 // also be affected.
442 NtlmClient client(NtlmFeatures(true));
443 std::vector<uint8_t> result = GenerateAuthMsg(client, test::kChallengeMsgV1);
444 ASSERT_FALSE(result.empty());
445
446 ASSERT_EQ(std::size(test::kExpectedAuthenticateMsgToOldV1ChallegeV2),
447 result.size());
448 ASSERT_EQ(0, memcmp(test::kExpectedAuthenticateMsgToOldV1ChallegeV2,
449 result.data(), result.size()));
450 }
451
452 // When the challenge message's target info is maximum size, adding new AV_PAIRs
453 // to the response will overflow SecurityBuffer. Test that we handle this.
TEST(NtlmClientTest,AvPairsOverflow)454 TEST(NtlmClientTest, AvPairsOverflow) {
455 {
456 NtlmClient client(NtlmFeatures(/*enable_NTLMv2=*/true));
457 std::vector<uint8_t> short_challenge;
458 ASSERT_NO_FATAL_FAILURE(MakeV2ChallengeMessage(0xfff, &short_challenge));
459 EXPECT_FALSE(GenerateAuthMsg(client, short_challenge).empty());
460 }
461 {
462 NtlmClient client(NtlmFeatures(/*enable_NTLMv2=*/true));
463 std::vector<uint8_t> long_challenge;
464 ASSERT_NO_FATAL_FAILURE(MakeV2ChallengeMessage(0xffff, &long_challenge));
465 EXPECT_TRUE(GenerateAuthMsg(client, long_challenge).empty());
466 }
467 }
468
469 } // namespace net::ntlm
470