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

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

Issue 2904633002: Replace NTLMv1 implementation with a functionally equivalent one.
Patch Set: Cleanup 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 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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698