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

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

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

Powered by Google App Engine
This is Rietveld 408576698