1 // Copyright 2017 The Chromium Authors
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.h>
8
9 #include "base/check_op.h"
10 #include "base/containers/span.h"
11 #include "base/logging.h"
12 #include "base/numerics/safe_math.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "net/ntlm/ntlm.h"
15 #include "net/ntlm/ntlm_buffer_reader.h"
16 #include "net/ntlm/ntlm_buffer_writer.h"
17 #include "net/ntlm/ntlm_constants.h"
18
19 namespace net::ntlm {
20
21 namespace {
22 // Parses the challenge message and returns the |challenge_flags| and
23 // |server_challenge| into the supplied buffer.
ParseChallengeMessage(base::span<const uint8_t> challenge_message,NegotiateFlags * challenge_flags,base::span<uint8_t,kChallengeLen> server_challenge)24 bool ParseChallengeMessage(
25 base::span<const uint8_t> challenge_message,
26 NegotiateFlags* challenge_flags,
27 base::span<uint8_t, kChallengeLen> server_challenge) {
28 NtlmBufferReader challenge_reader(challenge_message);
29
30 return challenge_reader.MatchMessageHeader(MessageType::kChallenge) &&
31 challenge_reader.SkipSecurityBufferWithValidation() &&
32 challenge_reader.ReadFlags(challenge_flags) &&
33 challenge_reader.ReadBytes(server_challenge);
34 }
35
36 // Parses the challenge message and extracts the information necessary to
37 // make an NTLMv2 response.
ParseChallengeMessageV2(base::span<const uint8_t> challenge_message,NegotiateFlags * challenge_flags,base::span<uint8_t,kChallengeLen> server_challenge,std::vector<AvPair> * av_pairs)38 bool ParseChallengeMessageV2(
39 base::span<const uint8_t> challenge_message,
40 NegotiateFlags* challenge_flags,
41 base::span<uint8_t, kChallengeLen> server_challenge,
42 std::vector<AvPair>* av_pairs) {
43 NtlmBufferReader challenge_reader(challenge_message);
44
45 return challenge_reader.MatchMessageHeader(MessageType::kChallenge) &&
46 challenge_reader.SkipSecurityBufferWithValidation() &&
47 challenge_reader.ReadFlags(challenge_flags) &&
48 challenge_reader.ReadBytes(server_challenge) &&
49 challenge_reader.SkipBytes(8) &&
50 // challenge_reader.ReadTargetInfoPayload(av_pairs);
51 (((*challenge_flags & NegotiateFlags::kTargetInfo) ==
52 NegotiateFlags::kTargetInfo)
53 ? challenge_reader.ReadTargetInfoPayload(av_pairs)
54 : true);
55 }
56
WriteAuthenticateMessage(NtlmBufferWriter * authenticate_writer,SecurityBuffer lm_payload,SecurityBuffer ntlm_payload,SecurityBuffer domain_payload,SecurityBuffer username_payload,SecurityBuffer hostname_payload,SecurityBuffer session_key_payload,NegotiateFlags authenticate_flags)57 bool WriteAuthenticateMessage(NtlmBufferWriter* authenticate_writer,
58 SecurityBuffer lm_payload,
59 SecurityBuffer ntlm_payload,
60 SecurityBuffer domain_payload,
61 SecurityBuffer username_payload,
62 SecurityBuffer hostname_payload,
63 SecurityBuffer session_key_payload,
64 NegotiateFlags authenticate_flags) {
65 return authenticate_writer->WriteMessageHeader(MessageType::kAuthenticate) &&
66 authenticate_writer->WriteSecurityBuffer(lm_payload) &&
67 authenticate_writer->WriteSecurityBuffer(ntlm_payload) &&
68 authenticate_writer->WriteSecurityBuffer(domain_payload) &&
69 authenticate_writer->WriteSecurityBuffer(username_payload) &&
70 authenticate_writer->WriteSecurityBuffer(hostname_payload) &&
71 authenticate_writer->WriteSecurityBuffer(session_key_payload) &&
72 authenticate_writer->WriteFlags(authenticate_flags);
73 }
74
75 // Writes the NTLMv1 LM Response and NTLM Response.
WriteResponsePayloads(NtlmBufferWriter * authenticate_writer,base::span<const uint8_t,kResponseLenV1> lm_response,base::span<const uint8_t,kResponseLenV1> ntlm_response)76 bool WriteResponsePayloads(
77 NtlmBufferWriter* authenticate_writer,
78 base::span<const uint8_t, kResponseLenV1> lm_response,
79 base::span<const uint8_t, kResponseLenV1> ntlm_response) {
80 return authenticate_writer->WriteBytes(lm_response) &&
81 authenticate_writer->WriteBytes(ntlm_response);
82 }
83
84 // Writes the |lm_response| and writes the NTLMv2 response by concatenating
85 // |v2_proof|, |v2_proof_input|, |updated_target_info| and 4 zero bytes.
WriteResponsePayloadsV2(NtlmBufferWriter * authenticate_writer,base::span<const uint8_t,kResponseLenV1> lm_response,base::span<const uint8_t,kNtlmProofLenV2> v2_proof,base::span<const uint8_t> v2_proof_input,base::span<const uint8_t> updated_target_info)86 bool WriteResponsePayloadsV2(
87 NtlmBufferWriter* authenticate_writer,
88 base::span<const uint8_t, kResponseLenV1> lm_response,
89 base::span<const uint8_t, kNtlmProofLenV2> v2_proof,
90 base::span<const uint8_t> v2_proof_input,
91 base::span<const uint8_t> updated_target_info) {
92 return authenticate_writer->WriteBytes(lm_response) &&
93 authenticate_writer->WriteBytes(v2_proof) &&
94 authenticate_writer->WriteBytes(v2_proof_input) &&
95 authenticate_writer->WriteBytes(updated_target_info) &&
96 authenticate_writer->WriteUInt32(0);
97 }
98
WriteStringPayloads(NtlmBufferWriter * authenticate_writer,bool is_unicode,const std::u16string & domain,const std::u16string & username,const std::string & hostname)99 bool WriteStringPayloads(NtlmBufferWriter* authenticate_writer,
100 bool is_unicode,
101 const std::u16string& domain,
102 const std::u16string& username,
103 const std::string& hostname) {
104 if (is_unicode) {
105 return authenticate_writer->WriteUtf16String(domain) &&
106 authenticate_writer->WriteUtf16String(username) &&
107 authenticate_writer->WriteUtf8AsUtf16String(hostname);
108 } else {
109 return authenticate_writer->WriteUtf16AsUtf8String(domain) &&
110 authenticate_writer->WriteUtf16AsUtf8String(username) &&
111 authenticate_writer->WriteUtf8String(hostname);
112 }
113 }
114
115 // Returns the size in bytes of a string16 depending whether unicode
116 // was negotiated.
GetStringPayloadLength(const std::u16string & str,bool is_unicode)117 size_t GetStringPayloadLength(const std::u16string& str, bool is_unicode) {
118 if (is_unicode)
119 return str.length() * 2;
120
121 // When |WriteUtf16AsUtf8String| is called with a |std::u16string|, the string
122 // is converted to UTF8. Do the conversion to ensure that the character
123 // count is correct.
124 return base::UTF16ToUTF8(str).length();
125 }
126
127 // Returns the size in bytes of a std::string depending whether unicode
128 // was negotiated.
GetStringPayloadLength(const std::string & str,bool is_unicode)129 size_t GetStringPayloadLength(const std::string& str, bool is_unicode) {
130 if (!is_unicode)
131 return str.length();
132
133 return base::UTF8ToUTF16(str).length() * 2;
134 }
135
136 // Sets |buffer| to point to |length| bytes from |offset| and updates |offset|
137 // past those bytes. In case of overflow, returns false.
ComputeSecurityBuffer(uint32_t * offset,size_t length,SecurityBuffer * buffer)138 bool ComputeSecurityBuffer(uint32_t* offset,
139 size_t length,
140 SecurityBuffer* buffer) {
141 base::CheckedNumeric<uint16_t> length_checked = length;
142 if (!length_checked.IsValid()) {
143 return false;
144 }
145 base::CheckedNumeric<uint32_t> new_offset = *offset + length_checked;
146 if (!new_offset.IsValid()) {
147 return false;
148 }
149 buffer->offset = *offset;
150 buffer->length = length_checked.ValueOrDie();
151 *offset = new_offset.ValueOrDie();
152 return true;
153 }
154
155 } // namespace
156
NtlmClient(NtlmFeatures features)157 NtlmClient::NtlmClient(NtlmFeatures features)
158 : features_(features), negotiate_flags_(kNegotiateMessageFlags) {
159 // Just generate the negotiate message once and hold on to it. It never
160 // changes and in NTLMv2 it's used as an input to the Message Integrity
161 // Check (MIC) in the Authenticate message.
162 GenerateNegotiateMessage();
163 }
164
165 NtlmClient::~NtlmClient() = default;
166
GetNegotiateMessage() const167 std::vector<uint8_t> NtlmClient::GetNegotiateMessage() const {
168 return negotiate_message_;
169 }
170
GenerateNegotiateMessage()171 void NtlmClient::GenerateNegotiateMessage() {
172 NtlmBufferWriter writer(kNegotiateMessageLen);
173 bool result =
174 writer.WriteMessageHeader(MessageType::kNegotiate) &&
175 writer.WriteFlags(negotiate_flags_) &&
176 writer.WriteSecurityBuffer(SecurityBuffer(kNegotiateMessageLen, 0)) &&
177 writer.WriteSecurityBuffer(SecurityBuffer(kNegotiateMessageLen, 0)) &&
178 writer.IsEndOfBuffer();
179
180 DCHECK(result);
181
182 negotiate_message_ = writer.Pass();
183 }
184
GenerateAuthenticateMessage(const std::u16string & domain,const std::u16string & username,const std::u16string & password,const std::string & hostname,const std::string & channel_bindings,const std::string & spn,uint64_t client_time,base::span<const uint8_t,kChallengeLen> client_challenge,base::span<const uint8_t> server_challenge_message) const185 std::vector<uint8_t> NtlmClient::GenerateAuthenticateMessage(
186 const std::u16string& domain,
187 const std::u16string& username,
188 const std::u16string& password,
189 const std::string& hostname,
190 const std::string& channel_bindings,
191 const std::string& spn,
192 uint64_t client_time,
193 base::span<const uint8_t, kChallengeLen> client_challenge,
194 base::span<const uint8_t> server_challenge_message) const {
195 // Limit the size of strings that are accepted. As an absolute limit any
196 // field represented by a |SecurityBuffer| or |AvPair| must be less than
197 // UINT16_MAX bytes long. The strings are restricted to the maximum sizes
198 // without regard to encoding. As such this isn't intended to restrict all
199 // invalid inputs, only to allow all possible valid inputs.
200 //
201 // |domain| and |hostname| can be no longer than 255 characters.
202 // |username| can be no longer than 104 characters. See [1].
203 // |password| can be no longer than 256 characters. See [2].
204 //
205 // [1] - https://technet.microsoft.com/en-us/library/bb726984.aspx
206 // [2] - https://technet.microsoft.com/en-us/library/cc512606.aspx
207 if (hostname.length() > kMaxFqdnLen || domain.length() > kMaxFqdnLen ||
208 username.length() > kMaxUsernameLen ||
209 password.length() > kMaxPasswordLen) {
210 return {};
211 }
212
213 NegotiateFlags challenge_flags;
214 uint8_t server_challenge[kChallengeLen];
215 uint8_t lm_response[kResponseLenV1];
216 uint8_t ntlm_response[kResponseLenV1];
217
218 // Response fields only for NTLMv2
219 std::vector<uint8_t> updated_target_info;
220 std::vector<uint8_t> v2_proof_input;
221 uint8_t v2_proof[kNtlmProofLenV2];
222 uint8_t v2_session_key[kSessionKeyLenV2];
223
224 if (IsNtlmV2()) {
225 std::vector<AvPair> av_pairs;
226 if (!ParseChallengeMessageV2(server_challenge_message, &challenge_flags,
227 server_challenge, &av_pairs)) {
228 return {};
229 }
230
231 uint64_t timestamp;
232 updated_target_info =
233 GenerateUpdatedTargetInfo(IsMicEnabled(), IsEpaEnabled(),
234 channel_bindings, spn, av_pairs, ×tamp);
235
236 memset(lm_response, 0, kResponseLenV1);
237 if (timestamp == UINT64_MAX) {
238 // If the server didn't send a time, then use the clients time.
239 timestamp = client_time;
240 }
241
242 uint8_t v2_hash[kNtlmHashLen];
243 GenerateNtlmHashV2(domain, username, password, v2_hash);
244 v2_proof_input = GenerateProofInputV2(timestamp, client_challenge);
245 GenerateNtlmProofV2(v2_hash, server_challenge,
246 base::make_span<kProofInputLenV2>(v2_proof_input),
247 updated_target_info, v2_proof);
248 GenerateSessionBaseKeyV2(v2_hash, v2_proof, v2_session_key);
249 } else {
250 if (!ParseChallengeMessage(server_challenge_message, &challenge_flags,
251 server_challenge)) {
252 return {};
253 }
254
255 // Calculate the responses for the authenticate message.
256 GenerateResponsesV1WithSessionSecurity(password, server_challenge,
257 client_challenge, lm_response,
258 ntlm_response);
259 }
260
261 // Always use extended session security even if the server tries to downgrade.
262 NegotiateFlags authenticate_flags = (challenge_flags & negotiate_flags_) |
263 NegotiateFlags::kExtendedSessionSecurity;
264
265 // Calculate all the payload lengths and offsets.
266 bool is_unicode = (authenticate_flags & NegotiateFlags::kUnicode) ==
267 NegotiateFlags::kUnicode;
268
269 SecurityBuffer lm_info;
270 SecurityBuffer ntlm_info;
271 SecurityBuffer domain_info;
272 SecurityBuffer username_info;
273 SecurityBuffer hostname_info;
274 SecurityBuffer session_key_info;
275 size_t authenticate_message_len;
276
277 if (!CalculatePayloadLayout(is_unicode, domain, username, hostname,
278 updated_target_info.size(), &lm_info, &ntlm_info,
279 &domain_info, &username_info, &hostname_info,
280 &session_key_info, &authenticate_message_len)) {
281 return {};
282 }
283
284 NtlmBufferWriter authenticate_writer(authenticate_message_len);
285 bool writer_result = WriteAuthenticateMessage(
286 &authenticate_writer, lm_info, ntlm_info, domain_info, username_info,
287 hostname_info, session_key_info, authenticate_flags);
288 DCHECK(writer_result);
289
290 if (IsNtlmV2()) {
291 // Write the optional (for V1) Version and MIC fields. Note that they
292 // could also safely be sent in V1. However, the server should never try to
293 // read them, because neither the version negotiate flag nor the
294 // |TargetInfoAvFlags::kMicPresent| in the target info are set.
295 //
296 // Version is never supported so it is filled with zeros. MIC is a hash
297 // calculated over all 3 messages while the MIC is set to zeros then
298 // backfilled at the end if the MIC feature is enabled.
299 writer_result = authenticate_writer.WriteZeros(kVersionFieldLen) &&
300 authenticate_writer.WriteZeros(kMicLenV2);
301
302 DCHECK(writer_result);
303 }
304
305 // Verify the location in the payload buffer.
306 DCHECK(authenticate_writer.GetCursor() == GetAuthenticateHeaderLength());
307 DCHECK(GetAuthenticateHeaderLength() == lm_info.offset);
308
309 if (IsNtlmV2()) {
310 // Write the response payloads for V2.
311 writer_result =
312 WriteResponsePayloadsV2(&authenticate_writer, lm_response, v2_proof,
313 v2_proof_input, updated_target_info);
314 } else {
315 // Write the response payloads.
316 DCHECK_EQ(kResponseLenV1, lm_info.length);
317 DCHECK_EQ(kResponseLenV1, ntlm_info.length);
318 writer_result =
319 WriteResponsePayloads(&authenticate_writer, lm_response, ntlm_response);
320 }
321
322 DCHECK(writer_result);
323 DCHECK_EQ(authenticate_writer.GetCursor(), domain_info.offset);
324
325 writer_result = WriteStringPayloads(&authenticate_writer, is_unicode, domain,
326 username, hostname);
327 DCHECK(writer_result);
328 DCHECK(authenticate_writer.IsEndOfBuffer());
329 DCHECK_EQ(authenticate_message_len, authenticate_writer.GetLength());
330
331 std::vector<uint8_t> auth_msg = authenticate_writer.Pass();
332
333 // Backfill the MIC if enabled.
334 if (IsMicEnabled()) {
335 // The MIC has to be generated over all 3 completed messages with the MIC
336 // set to zeros.
337 DCHECK_LT(kMicOffsetV2 + kMicLenV2, authenticate_message_len);
338
339 base::span<uint8_t, kMicLenV2> mic(
340 const_cast<uint8_t*>(auth_msg.data()) + kMicOffsetV2, kMicLenV2);
341 GenerateMicV2(v2_session_key, negotiate_message_, server_challenge_message,
342 auth_msg, mic);
343 }
344
345 return auth_msg;
346 }
347
CalculatePayloadLayout(bool is_unicode,const std::u16string & domain,const std::u16string & username,const std::string & hostname,size_t updated_target_info_len,SecurityBuffer * lm_info,SecurityBuffer * ntlm_info,SecurityBuffer * domain_info,SecurityBuffer * username_info,SecurityBuffer * hostname_info,SecurityBuffer * session_key_info,size_t * authenticate_message_len) const348 bool NtlmClient::CalculatePayloadLayout(
349 bool is_unicode,
350 const std::u16string& domain,
351 const std::u16string& username,
352 const std::string& hostname,
353 size_t updated_target_info_len,
354 SecurityBuffer* lm_info,
355 SecurityBuffer* ntlm_info,
356 SecurityBuffer* domain_info,
357 SecurityBuffer* username_info,
358 SecurityBuffer* hostname_info,
359 SecurityBuffer* session_key_info,
360 size_t* authenticate_message_len) const {
361 uint32_t offset = GetAuthenticateHeaderLength();
362 if (!ComputeSecurityBuffer(&offset, 0, session_key_info) ||
363 !ComputeSecurityBuffer(&offset, kResponseLenV1, lm_info) ||
364 !ComputeSecurityBuffer(
365 &offset, GetNtlmResponseLength(updated_target_info_len), ntlm_info) ||
366 !ComputeSecurityBuffer(
367 &offset, GetStringPayloadLength(domain, is_unicode), domain_info) ||
368 !ComputeSecurityBuffer(&offset,
369 GetStringPayloadLength(username, is_unicode),
370 username_info) ||
371 !ComputeSecurityBuffer(&offset,
372 GetStringPayloadLength(hostname, is_unicode),
373 hostname_info)) {
374 return false;
375 }
376
377 *authenticate_message_len = offset;
378 return true;
379 }
380
GetAuthenticateHeaderLength() const381 size_t NtlmClient::GetAuthenticateHeaderLength() const {
382 if (IsNtlmV2()) {
383 return kAuthenticateHeaderLenV2;
384 }
385
386 return kAuthenticateHeaderLenV1;
387 }
388
GetNtlmResponseLength(size_t updated_target_info_len) const389 size_t NtlmClient::GetNtlmResponseLength(size_t updated_target_info_len) const {
390 if (IsNtlmV2()) {
391 return kNtlmResponseHeaderLenV2 + updated_target_info_len + 4;
392 }
393
394 return kResponseLenV1;
395 }
396
397 } // namespace net::ntlm
398