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 Buffer GenerateAuthMsg(const NtlmClient& client, const Buffer& challenge_msg) { | |
24 return client.GenerateAuthenticateMessage( | |
25 test::kNtlmDomain, test::kUser, test::kPassword, test::kHostnameAscii, | |
26 test::kClientChallenge, challenge_msg); | |
27 } | |
28 | |
29 Buffer GenerateAuthMsg(const NtlmClient& client, | |
30 const uint8_t* challenge_msg, | |
31 size_t challenge_msg_len) { | |
32 return GenerateAuthMsg(client, Buffer(challenge_msg, challenge_msg_len)); | |
33 } | |
34 | |
35 Buffer GenerateAuthMsg(const NtlmClient& client, | |
36 const NtlmBufferWriter& challenge_writer) { | |
37 return GenerateAuthMsg(client, challenge_writer.GetBuffer()); | |
38 } | |
39 | |
40 bool GetAuthMsgResult(const NtlmClient& client, | |
41 const NtlmBufferWriter& challenge_writer) { | |
42 return !GenerateAuthMsg(client, challenge_writer).empty(); | |
43 } | |
44 | |
45 bool ReadBytesPayload(NtlmBufferReader* reader, uint8_t* buffer, size_t len) { | |
46 SecurityBuffer sec_buf; | |
47 return reader->ReadSecurityBuffer(&sec_buf) && (sec_buf.length == len) && | |
48 reader->ReadBytesFrom(sec_buf, buffer); | |
49 } | |
50 | |
51 // Reads bytes from a payload and assigns them to a string. This makes | |
52 // no assumptions about the underlying encoding. | |
53 bool ReadStringPayload(NtlmBufferReader* reader, std::string* str) { | |
54 SecurityBuffer sec_buf; | |
55 if (!reader->ReadSecurityBuffer(&sec_buf)) | |
56 return false; | |
57 | |
58 uint8_t raw[sec_buf.length]; | |
asanka
2017/07/20 19:35:46
I thought we weren't going to do this? Perhaps std
zentaro
2017/08/01 18:21:36
Done.
| |
59 if (!reader->ReadBytesFrom(sec_buf, raw)) | |
60 return false; | |
61 | |
62 str->assign(reinterpret_cast<const char*>(raw), sec_buf.length); | |
63 return true; | |
64 } | |
65 | |
66 // Reads bytes from a payload and assigns them to a string16. This makes | |
67 // no assumptions about the underlying encoding. This will fail if there | |
68 // are an odd number of bytes in the payload. | |
69 bool ReadString16Payload(NtlmBufferReader* reader, base::string16* str) { | |
70 SecurityBuffer sec_buf; | |
71 if (!reader->ReadSecurityBuffer(&sec_buf) || (sec_buf.length % 2 != 0)) | |
72 return false; | |
73 | |
74 uint8_t raw[sec_buf.length]; | |
75 if (!reader->ReadBytesFrom(sec_buf, raw)) | |
76 return false; | |
77 | |
78 #if defined(ARCH_CPU_BIG_ENDIAN) | |
79 for (size_t i = 0; i < sec_buf.length; i += 2) { | |
80 std::swap(raw[i], raw[i + 1]); | |
81 } | |
82 #endif | |
83 | |
84 str->assign(reinterpret_cast<const base::char16*>(raw), sec_buf.length / 2); | |
85 return true; | |
86 } | |
87 | |
88 } // namespace | |
89 | |
90 TEST(NtlmClientTest, VerifyNegotiateMessageV1) { | |
91 NtlmClient client; | |
92 | |
93 Buffer result = client.GetNegotiateMessage(); | |
94 | |
95 ASSERT_EQ(kNegotiateMessageLen, result.size()); | |
96 ASSERT_EQ(0, memcmp(test::kExpectedNegotiateMsg, result.data(), | |
97 kNegotiateMessageLen)); | |
98 } | |
99 | |
100 TEST(NtlmClientTest, MinimalStructurallyValidChallenge) { | |
101 NtlmClient client; | |
102 | |
103 NtlmBufferWriter writer(kMinChallengeHeaderLen); | |
104 ASSERT_TRUE( | |
105 writer.WriteBytes(test::kMinChallengeMessage, kMinChallengeHeaderLen)); | |
106 | |
107 ASSERT_TRUE(GetAuthMsgResult(client, writer)); | |
108 } | |
109 | |
110 TEST(NtlmClientTest, MinimalStructurallyValidChallengeZeroOffset) { | |
111 NtlmClient client; | |
112 | |
113 // The spec (2.2.1.2) states that the length SHOULD be 0 and the offset | |
114 // SHOULD be where the payload would be if it was present. This is the | |
115 // expected response from a compliant server when no target name is sent. | |
116 // In reality the offset should always be ignored if the length is zero. | |
117 // Also implementations often just write zeros. | |
118 uint8_t raw[kMinChallengeHeaderLen]; | |
119 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen); | |
120 // Modify the default valid message to overwrite the offset to zero. | |
121 raw[16] = 0x00; | |
asanka
2017/07/20 19:35:46
Just for giggles, let's assert that raw[16] wasn't
zentaro
2017/08/01 18:21:36
Done.
| |
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 raw[7] = 0xff; | |
148 NtlmBufferWriter writer(kMinChallengeHeaderLen); | |
149 ASSERT_TRUE(writer.WriteBytes(raw, arraysize(raw))); | |
150 ASSERT_FALSE(GetAuthMsgResult(client, writer)); | |
151 } | |
152 | |
153 TEST(NtlmClientTest, ChallengeMsgWrongMessageType) { | |
154 NtlmClient client; | |
155 | |
156 // Fail because the message type should be MessageType::kChallenge | |
157 // (0x00000002) | |
158 uint8_t raw[kMinChallengeHeaderLen]; | |
159 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen); | |
160 // Modify the message type. | |
161 raw[8] = 0x03; | |
162 | |
163 NtlmBufferWriter writer(kMinChallengeHeaderLen); | |
164 ASSERT_TRUE(writer.WriteBytes(raw, arraysize(raw))); | |
165 | |
166 ASSERT_FALSE(GetAuthMsgResult(client, writer)); | |
167 } | |
168 | |
169 TEST(NtlmClientTest, ChallengeWithNoTargetName) { | |
170 NtlmClient client; | |
171 | |
172 // The spec (2.2.1.2) states that the length SHOULD be 0 and the offset | |
173 // SHOULD be where the payload would be if it was present. This is the | |
174 // expected response from a compliant server when no target name is sent. | |
175 // In reality the offset should always be ignored if the length is zero. | |
176 // Also implementations often just write zeros. | |
177 uint8_t raw[kMinChallengeHeaderLen]; | |
178 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen); | |
179 // Modify the default valid message to overwrite the offset to zero. | |
180 raw[16] = 0x00; | |
181 | |
182 NtlmBufferWriter writer(kMinChallengeHeaderLen); | |
183 ASSERT_TRUE(writer.WriteBytes(raw, arraysize(raw))); | |
184 | |
185 ASSERT_TRUE(GetAuthMsgResult(client, writer)); | |
186 } | |
187 | |
188 TEST(NtlmClientTest, Type2MessageWithTargetName) { | |
189 NtlmClient client; | |
190 | |
191 // One extra byte is provided for target name. | |
192 uint8_t raw[kMinChallengeHeaderLen + 1]; | |
193 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen); | |
194 // Modify the default valid message to indicate 1 byte is present in the | |
195 // target name payload. | |
196 raw[12] = 0x01; | |
197 raw[14] = 0x01; | |
198 // Put something in the target name. | |
199 raw[32] = 'Z'; | |
200 | |
201 NtlmBufferWriter writer(kChallengeHeaderLen + 1); | |
202 ASSERT_TRUE(writer.WriteBytes(raw, arraysize(raw))); | |
203 | |
204 ASSERT_TRUE(GetAuthMsgResult(client, writer)); | |
205 } | |
206 | |
207 TEST(NtlmClientTest, NoTargetNameOverflowFromOffset) { | |
208 NtlmClient client; | |
209 | |
210 uint8_t raw[kMinChallengeHeaderLen]; | |
211 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen); | |
212 // Modify the default valid message to claim that the target name field is 1 | |
213 // byte long overrunning the end of the message message. | |
214 raw[12] = 0x01; | |
215 raw[14] = 0x01; | |
216 | |
217 NtlmBufferWriter writer(kMinChallengeHeaderLen); | |
218 ASSERT_TRUE(writer.WriteBytes(raw, arraysize(raw))); | |
219 | |
220 // The above malformed message could cause an implementation to read outside | |
221 // the message buffer because the offset is past the end of the message. | |
222 // Verify it gets rejected. | |
223 ASSERT_FALSE(GetAuthMsgResult(client, writer)); | |
224 } | |
225 | |
226 TEST(NtlmClientTest, NoTargetNameOverflowFromLength) { | |
227 NtlmClient client; | |
228 | |
229 // Message has 1 extra byte of space after the header for the target name. | |
230 // One extra byte is provided for target name. | |
231 uint8_t raw[kMinChallengeHeaderLen + 1]; | |
232 memcpy(raw, test::kMinChallengeMessage, kMinChallengeHeaderLen); | |
233 // Modify the default valid message to indicate 2 bytes are present in the | |
234 // target name payload (however there is only space for 1). | |
235 raw[12] = 0x02; | |
236 raw[14] = 0x02; | |
237 // Put something in the target name. | |
238 raw[32] = 'Z'; | |
239 | |
240 NtlmBufferWriter writer(kMinChallengeHeaderLen + 1); | |
241 ASSERT_TRUE(writer.WriteBytes(raw, arraysize(raw))); | |
242 | |
243 // The above malformed message could cause an implementation | |
244 // to read outside the message buffer because the length is | |
245 // longer than available space. Verify it gets rejected. | |
246 ASSERT_FALSE(GetAuthMsgResult(client, writer)); | |
247 } | |
248 | |
249 TEST(NtlmClientTest, Type3UnicodeWithSessionSecuritySpecTest) { | |
250 NtlmClient client; | |
251 | |
252 Buffer result = GenerateAuthMsg(client, test::kChallengeMsgV1, | |
253 arraysize(test::kChallengeMsgV1)); | |
254 | |
255 ASSERT_FALSE(result.empty()); | |
256 ASSERT_EQ(arraysize(test::kExpectedAuthenticateMsgV1), result.size()); | |
257 ASSERT_EQ(0, memcmp(test::kExpectedAuthenticateMsgV1, result.data(), | |
258 result.size())); | |
259 } | |
260 | |
261 TEST(NtlmClientTest, Type3WithoutUnicode) { | |
262 NtlmClient client; | |
263 | |
264 Buffer result = GenerateAuthMsg(client, test::kMinChallengeMessageNoUnicode, | |
265 kMinChallengeHeaderLen); | |
266 ASSERT_FALSE(result.empty()); | |
267 | |
268 NtlmBufferReader reader(result); | |
269 ASSERT_TRUE(reader.MatchMessageHeader(MessageType::kAuthenticate)); | |
270 | |
271 // Read the LM and NTLM Response Payloads. | |
272 uint8_t actual_lm_response[kResponseLenV1]; | |
273 uint8_t actual_ntlm_response[kResponseLenV1]; | |
274 | |
275 ASSERT_TRUE(ReadBytesPayload(&reader, actual_lm_response, kResponseLenV1)); | |
276 ASSERT_TRUE(ReadBytesPayload(&reader, actual_ntlm_response, kResponseLenV1)); | |
277 | |
278 ASSERT_EQ(0, memcmp(test::kExpectedLmResponseWithV1SS, actual_lm_response, | |
279 kResponseLenV1)); | |
280 ASSERT_EQ(0, memcmp(test::kExpectedNtlmResponseWithV1SS, actual_ntlm_response, | |
281 kResponseLenV1)); | |
282 | |
283 std::string domain; | |
284 std::string username; | |
285 std::string hostname; | |
286 ASSERT_TRUE(ReadStringPayload(&reader, &domain)); | |
287 ASSERT_EQ(test::kNtlmDomainAscii, domain); | |
288 ASSERT_TRUE(ReadStringPayload(&reader, &username)); | |
289 ASSERT_EQ(test::kUserAscii, username); | |
290 ASSERT_TRUE(ReadStringPayload(&reader, &hostname)); | |
291 ASSERT_EQ(test::kHostnameAscii, hostname); | |
292 | |
293 // The session key is not used in HTTP. Since NTLMSSP_NEGOTIATE_KEY_EXCH | |
294 // was not sent this is empty. | |
295 ASSERT_TRUE(reader.MatchEmptySecurityBuffer()); | |
296 | |
297 // Verify the unicode flag is not set and OEM flag is. | |
298 NegotiateFlags flags; | |
299 ASSERT_TRUE(reader.ReadFlags(&flags)); | |
300 ASSERT_EQ(NegotiateFlags::kNone, flags & NegotiateFlags::kUnicode); | |
301 ASSERT_EQ(NegotiateFlags::kOem, flags & NegotiateFlags::kOem); | |
302 } | |
303 | |
304 TEST(NtlmClientTest, ClientDoesNotDowngradeSessionSecurity) { | |
305 NtlmClient client; | |
306 | |
307 Buffer result = GenerateAuthMsg(client, test::kMinChallengeMessageNoSS, | |
308 kMinChallengeHeaderLen); | |
309 ASSERT_FALSE(result.empty()); | |
310 | |
311 NtlmBufferReader reader(result); | |
312 ASSERT_TRUE(reader.MatchMessageHeader(MessageType::kAuthenticate)); | |
313 | |
314 // Read the LM and NTLM Response Payloads. | |
315 uint8_t actual_lm_response[kResponseLenV1]; | |
316 uint8_t actual_ntlm_response[kResponseLenV1]; | |
317 | |
318 ASSERT_TRUE(ReadBytesPayload(&reader, actual_lm_response, kResponseLenV1)); | |
319 ASSERT_TRUE(ReadBytesPayload(&reader, actual_ntlm_response, kResponseLenV1)); | |
320 | |
321 // The important part of this test is that even though the | |
322 // server told the client to drop session security. The client | |
323 // DID NOT drop it. | |
324 ASSERT_EQ(0, memcmp(test::kExpectedLmResponseWithV1SS, actual_lm_response, | |
325 kResponseLenV1)); | |
326 ASSERT_EQ(0, memcmp(test::kExpectedNtlmResponseWithV1SS, actual_ntlm_response, | |
327 kResponseLenV1)); | |
328 | |
329 base::string16 domain; | |
330 base::string16 username; | |
331 base::string16 hostname; | |
332 ASSERT_TRUE(ReadString16Payload(&reader, &domain)); | |
333 ASSERT_EQ(test::kNtlmDomain, domain); | |
334 ASSERT_TRUE(ReadString16Payload(&reader, &username)); | |
335 ASSERT_EQ(test::kUser, username); | |
336 ASSERT_TRUE(ReadString16Payload(&reader, &hostname)); | |
337 ASSERT_EQ(test::kHostname, hostname); | |
338 | |
339 // The session key is not used in HTTP. Since NTLMSSP_NEGOTIATE_KEY_EXCH | |
340 // was not sent this is empty. | |
341 ASSERT_TRUE(reader.MatchEmptySecurityBuffer()); | |
342 | |
343 // Verify the unicode and session security flag is set. | |
344 NegotiateFlags flags; | |
345 ASSERT_TRUE(reader.ReadFlags(&flags)); | |
346 ASSERT_EQ(NegotiateFlags::kUnicode, flags & NegotiateFlags::kUnicode); | |
347 ASSERT_EQ(NegotiateFlags::kExtendedSessionSecurity, | |
348 flags & NegotiateFlags::kExtendedSessionSecurity); | |
349 } | |
350 | |
351 } // namespace ntlm | |
352 } // namespace net | |
OLD | NEW |