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

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

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

Powered by Google App Engine
This is Rietveld 408576698