OLD | NEW |
---|---|
(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 | |
OLD | NEW |