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