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