• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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