Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6)

Side by Side Diff: net/ntlm/ntlm_client_unittest.cc

Issue 2904633002: Replace NTLMv1 implementation with a functionally equivalent one.
Patch Set: Fix uninitialized read Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « net/ntlm/ntlm_client_fuzzer.cc ('k') | net/ntlm/ntlm_constants.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2017 The Chromium Authors. All rights reserved.
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 "build/build_config.h"
10 #include "net/ntlm/ntlm.h"
11 #include "net/ntlm/ntlm_buffer_reader.h"
12 #include "net/ntlm/ntlm_buffer_writer.h"
13 #include "net/ntlm/ntlm_test_data.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15
16 namespace net {
17 namespace ntlm {
18
19 namespace {
20
21 Buffer GenerateAuthMsg(const NtlmClient& client, const Buffer& challenge_msg) {
22 return client.GenerateAuthenticateMessage(
23 test::kNtlmDomain, test::kUser, test::kPassword, test::kHostnameAscii,
24 test::kClientChallenge, challenge_msg);
25 }
26
27 Buffer GenerateAuthMsg(const NtlmClient& client,
28 const uint8_t* challenge_msg,
29 size_t challenge_msg_len) {
30 return GenerateAuthMsg(client, Buffer(challenge_msg, challenge_msg_len));
31 }
32
33 Buffer GenerateAuthMsg(const NtlmClient& client,
34 const NtlmBufferWriter& challenge_writer) {
35 return GenerateAuthMsg(client, challenge_writer.GetBuffer());
36 }
37
38 bool GetAuthMsgResult(const NtlmClient& client,
39 const NtlmBufferWriter& challenge_writer) {
40 return !GenerateAuthMsg(client, challenge_writer).empty();
41 }
42
43 bool ReadBytesPayload(NtlmBufferReader* reader, uint8_t* buffer, size_t len) {
44 SecurityBuffer sec_buf;
45 return reader->ReadSecurityBuffer(&sec_buf) && (sec_buf.length == len) &&
46 reader->ReadBytesFrom(sec_buf, buffer);
47 }
48
49 // Reads bytes from a payload and assigns them to a string. This makes
50 // no assumptions about the underlying encoding.
51 bool ReadStringPayload(NtlmBufferReader* reader, std::string* str) {
52 SecurityBuffer sec_buf;
53 if (!reader->ReadSecurityBuffer(&sec_buf))
54 return false;
55
56 std::unique_ptr<uint8_t[]> raw(new uint8_t[sec_buf.length]);
57 if (!reader->ReadBytesFrom(sec_buf, raw.get()))
58 return false;
59
60 str->assign(reinterpret_cast<const char*>(raw.get()), sec_buf.length);
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.
67 bool ReadString16Payload(NtlmBufferReader* reader, base::string16* str) {
68 SecurityBuffer sec_buf;
69 if (!reader->ReadSecurityBuffer(&sec_buf) || (sec_buf.length % 2 != 0))
70 return false;
71
72 std::unique_ptr<uint8_t[]> raw(new uint8_t[sec_buf.length]);
73 if (!reader->ReadBytesFrom(sec_buf, raw.get()))
74 return false;
75
76 #if defined(ARCH_CPU_BIG_ENDIAN)
77 for (size_t i = 0; i < sec_buf.length; i += 2) {
78 std::swap(raw.get()[i], raw.get()[i + 1]);
79 }
80 #endif
81
82 str->assign(reinterpret_cast<const base::char16*>(raw.get()),
83 sec_buf.length / 2);
84 return true;
85 }
86
87 } // namespace
88
89 TEST(NtlmClientTest, VerifyNegotiateMessageV1) {
90 NtlmClient client;
91
92 Buffer result = client.GetNegotiateMessage();
93
94 ASSERT_EQ(kNegotiateMessageLen, result.size());
95 ASSERT_EQ(0, memcmp(test::kExpectedNegotiateMsg, result.data(),
96 kNegotiateMessageLen));
97 }
98
99 TEST(NtlmClientTest, MinimalStructurallyValidChallenge) {
100 NtlmClient client;
101
102 NtlmBufferWriter writer(kMinChallengeHeaderLen);
103 ASSERT_TRUE(
104 writer.WriteBytes(test::kMinChallengeMessage, kMinChallengeHeaderLen));
105
106 ASSERT_TRUE(GetAuthMsgResult(client, writer));
107 }
108
109 TEST(NtlmClientTest, MinimalStructurallyValidChallengeZeroOffset) {
110 NtlmClient client;
111
112 // The spec (2.2.1.2) states that the length SHOULD be 0 and the offset
113 // SHOULD be where the payload would be if it was present. This is the
114 // expected response from a compliant server when no target name is sent.
115 // In reality the offset should always be ignored if the length is zero.
116 // Also implementations often just write zeros.
117 uint8_t raw[kMinChallengeHeaderLen];
118 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen);
119 // Modify the default valid message to overwrite the offset to zero.
120 ASSERT_NE(0x00, raw[16]);
121 raw[16] = 0x00;
122
123 NtlmBufferWriter writer(kMinChallengeHeaderLen);
124 ASSERT_TRUE(writer.WriteBytes(raw, arraysize(raw)));
125
126 ASSERT_TRUE(GetAuthMsgResult(client, writer));
127 }
128
129 TEST(NtlmClientTest, ChallengeMsgTooShort) {
130 NtlmClient client;
131
132 // Fail because the minimum size valid message is 32 bytes.
133 NtlmBufferWriter writer(kMinChallengeHeaderLen - 1);
134 ASSERT_TRUE(writer.WriteBytes(test::kMinChallengeMessage,
135 kMinChallengeHeaderLen - 1));
136 ASSERT_FALSE(GetAuthMsgResult(client, writer));
137 }
138
139 TEST(NtlmClientTest, ChallengeMsgNoSig) {
140 NtlmClient client;
141
142 // Fail because the first 8 bytes don't match "NTLMSSP\0"
143 uint8_t raw[kMinChallengeHeaderLen];
144 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen);
145 // Modify the default valid message to overwrite the last byte of the
146 // signature.
147 ASSERT_NE(0xff, raw[7]);
148 raw[7] = 0xff;
149 NtlmBufferWriter writer(kMinChallengeHeaderLen);
150 ASSERT_TRUE(writer.WriteBytes(raw, arraysize(raw)));
151 ASSERT_FALSE(GetAuthMsgResult(client, writer));
152 }
153
154 TEST(NtlmClientTest, ChallengeMsgWrongMessageType) {
155 NtlmClient client;
156
157 // Fail because the message type should be MessageType::kChallenge
158 // (0x00000002)
159 uint8_t raw[kMinChallengeHeaderLen];
160 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen);
161 // Modify the message type.
162 ASSERT_NE(0x03, raw[8]);
163 raw[8] = 0x03;
164
165 NtlmBufferWriter writer(kMinChallengeHeaderLen);
166 ASSERT_TRUE(writer.WriteBytes(raw, arraysize(raw)));
167
168 ASSERT_FALSE(GetAuthMsgResult(client, writer));
169 }
170
171 TEST(NtlmClientTest, ChallengeWithNoTargetName) {
172 NtlmClient client;
173
174 // The spec (2.2.1.2) states that the length SHOULD be 0 and the offset
175 // SHOULD be where the payload would be if it was present. This is the
176 // expected response from a compliant server when no target name is sent.
177 // In reality the offset should always be ignored if the length is zero.
178 // Also implementations often just write zeros.
179 uint8_t raw[kMinChallengeHeaderLen];
180 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen);
181 // Modify the default valid message to overwrite the offset to zero.
182 ASSERT_NE(0x00, raw[16]);
183 raw[16] = 0x00;
184
185 NtlmBufferWriter writer(kMinChallengeHeaderLen);
186 ASSERT_TRUE(writer.WriteBytes(raw, arraysize(raw)));
187
188 ASSERT_TRUE(GetAuthMsgResult(client, writer));
189 }
190
191 TEST(NtlmClientTest, Type2MessageWithTargetName) {
192 NtlmClient client;
193
194 // One extra byte is provided for target name.
195 uint8_t raw[kMinChallengeHeaderLen + 1];
196 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen);
197 // Put something in the target name.
198 raw[kMinChallengeHeaderLen] = 'Z';
199
200 // Modify the default valid message to indicate 1 byte is present in the
201 // target name payload.
202 ASSERT_NE(0x01, raw[12]);
203 ASSERT_EQ(0x00, raw[13]);
204 ASSERT_NE(0x01, raw[14]);
205 ASSERT_EQ(0x00, raw[15]);
206 raw[12] = 0x01;
207 raw[14] = 0x01;
208
209 NtlmBufferWriter writer(kChallengeHeaderLen + 1);
210 ASSERT_TRUE(writer.WriteBytes(raw, arraysize(raw)));
211
212 ASSERT_TRUE(GetAuthMsgResult(client, writer));
213 }
214
215 TEST(NtlmClientTest, NoTargetNameOverflowFromOffset) {
216 NtlmClient client;
217
218 uint8_t raw[kMinChallengeHeaderLen];
219 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen);
220 // Modify the default valid message to claim that the target name field is 1
221 // byte long overrunning the end of the message message.
222 ASSERT_NE(0x01, raw[12]);
223 ASSERT_EQ(0x00, raw[13]);
224 ASSERT_NE(0x01, raw[14]);
225 ASSERT_EQ(0x00, raw[15]);
226 raw[12] = 0x01;
227 raw[14] = 0x01;
228
229 NtlmBufferWriter writer(kMinChallengeHeaderLen);
230 ASSERT_TRUE(writer.WriteBytes(raw, arraysize(raw)));
231
232 // The above malformed message could cause an implementation to read outside
233 // the message buffer because the offset is past the end of the message.
234 // Verify it gets rejected.
235 ASSERT_FALSE(GetAuthMsgResult(client, writer));
236 }
237
238 TEST(NtlmClientTest, NoTargetNameOverflowFromLength) {
239 NtlmClient client;
240
241 // Message has 1 extra byte of space after the header for the target name.
242 // One extra byte is provided for target name.
243 uint8_t raw[kMinChallengeHeaderLen + 1];
244 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen);
245 // Put something in the target name.
246 raw[kMinChallengeHeaderLen] = 'Z';
247
248 // Modify the default valid message to indicate 2 bytes are present in the
249 // target name payload (however there is only space for 1).
250 ASSERT_NE(0x02, raw[12]);
251 ASSERT_EQ(0x00, raw[13]);
252 ASSERT_NE(0x02, raw[14]);
253 ASSERT_EQ(0x00, raw[15]);
254 raw[12] = 0x02;
255 raw[14] = 0x02;
256
257 NtlmBufferWriter writer(kMinChallengeHeaderLen + 1);
258 ASSERT_TRUE(writer.WriteBytes(raw, arraysize(raw)));
259
260 // The above malformed message could cause an implementation
261 // to read outside the message buffer because the length is
262 // longer than available space. Verify it gets rejected.
263 ASSERT_FALSE(GetAuthMsgResult(client, writer));
264 }
265
266 TEST(NtlmClientTest, Type3UnicodeWithSessionSecuritySpecTest) {
267 NtlmClient client;
268
269 Buffer result = GenerateAuthMsg(client, test::kChallengeMsgV1,
270 arraysize(test::kChallengeMsgV1));
271
272 ASSERT_FALSE(result.empty());
273 ASSERT_EQ(arraysize(test::kExpectedAuthenticateMsgV1), result.size());
274 ASSERT_EQ(0, memcmp(test::kExpectedAuthenticateMsgV1, result.data(),
275 result.size()));
276 }
277
278 TEST(NtlmClientTest, Type3WithoutUnicode) {
279 NtlmClient client;
280
281 Buffer result = GenerateAuthMsg(client, test::kMinChallengeMessageNoUnicode,
282 kMinChallengeHeaderLen);
283 ASSERT_FALSE(result.empty());
284
285 NtlmBufferReader reader(result);
286 ASSERT_TRUE(reader.MatchMessageHeader(MessageType::kAuthenticate));
287
288 // Read the LM and NTLM Response Payloads.
289 uint8_t actual_lm_response[kResponseLenV1];
290 uint8_t actual_ntlm_response[kResponseLenV1];
291
292 ASSERT_TRUE(ReadBytesPayload(&reader, actual_lm_response, kResponseLenV1));
293 ASSERT_TRUE(ReadBytesPayload(&reader, actual_ntlm_response, kResponseLenV1));
294
295 ASSERT_EQ(0, memcmp(test::kExpectedLmResponseWithV1SS, actual_lm_response,
296 kResponseLenV1));
297 ASSERT_EQ(0, memcmp(test::kExpectedNtlmResponseWithV1SS, actual_ntlm_response,
298 kResponseLenV1));
299
300 std::string domain;
301 std::string username;
302 std::string hostname;
303 ASSERT_TRUE(ReadStringPayload(&reader, &domain));
304 ASSERT_EQ(test::kNtlmDomainAscii, domain);
305 ASSERT_TRUE(ReadStringPayload(&reader, &username));
306 ASSERT_EQ(test::kUserAscii, username);
307 ASSERT_TRUE(ReadStringPayload(&reader, &hostname));
308 ASSERT_EQ(test::kHostnameAscii, hostname);
309
310 // The session key is not used in HTTP. Since NTLMSSP_NEGOTIATE_KEY_EXCH
311 // was not sent this is empty.
312 ASSERT_TRUE(reader.MatchEmptySecurityBuffer());
313
314 // Verify the unicode flag is not set and OEM flag is.
315 NegotiateFlags flags;
316 ASSERT_TRUE(reader.ReadFlags(&flags));
317 ASSERT_EQ(NegotiateFlags::kNone, flags & NegotiateFlags::kUnicode);
318 ASSERT_EQ(NegotiateFlags::kOem, flags & NegotiateFlags::kOem);
319 }
320
321 TEST(NtlmClientTest, ClientDoesNotDowngradeSessionSecurity) {
322 NtlmClient client;
323
324 Buffer result = GenerateAuthMsg(client, test::kMinChallengeMessageNoSS,
325 kMinChallengeHeaderLen);
326 ASSERT_FALSE(result.empty());
327
328 NtlmBufferReader reader(result);
329 ASSERT_TRUE(reader.MatchMessageHeader(MessageType::kAuthenticate));
330
331 // Read the LM and NTLM Response Payloads.
332 uint8_t actual_lm_response[kResponseLenV1];
333 uint8_t actual_ntlm_response[kResponseLenV1];
334
335 ASSERT_TRUE(ReadBytesPayload(&reader, actual_lm_response, kResponseLenV1));
336 ASSERT_TRUE(ReadBytesPayload(&reader, actual_ntlm_response, kResponseLenV1));
337
338 // The important part of this test is that even though the
339 // server told the client to drop session security. The client
340 // DID NOT drop it.
341 ASSERT_EQ(0, memcmp(test::kExpectedLmResponseWithV1SS, actual_lm_response,
342 kResponseLenV1));
343 ASSERT_EQ(0, memcmp(test::kExpectedNtlmResponseWithV1SS, actual_ntlm_response,
344 kResponseLenV1));
345
346 base::string16 domain;
347 base::string16 username;
348 base::string16 hostname;
349 ASSERT_TRUE(ReadString16Payload(&reader, &domain));
350 ASSERT_EQ(test::kNtlmDomain, domain);
351 ASSERT_TRUE(ReadString16Payload(&reader, &username));
352 ASSERT_EQ(test::kUser, username);
353 ASSERT_TRUE(ReadString16Payload(&reader, &hostname));
354 ASSERT_EQ(test::kHostname, hostname);
355
356 // The session key is not used in HTTP. Since NTLMSSP_NEGOTIATE_KEY_EXCH
357 // was not sent this is empty.
358 ASSERT_TRUE(reader.MatchEmptySecurityBuffer());
359
360 // Verify the unicode and session security flag is set.
361 NegotiateFlags flags;
362 ASSERT_TRUE(reader.ReadFlags(&flags));
363 ASSERT_EQ(NegotiateFlags::kUnicode, flags & NegotiateFlags::kUnicode);
364 ASSERT_EQ(NegotiateFlags::kExtendedSessionSecurity,
365 flags & NegotiateFlags::kExtendedSessionSecurity);
366 }
367
368 } // namespace ntlm
369 } // namespace net
OLDNEW
« no previous file with comments | « net/ntlm/ntlm_client_fuzzer.cc ('k') | net/ntlm/ntlm_constants.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698