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