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