• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, &timestamp);
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