• 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 #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, &timestamp);
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