xref: /aosp_15_r20/external/cronet/net/ntlm/ntlm.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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.h"
6 
7 #include <string.h>
8 
9 #include "base/check_op.h"
10 #include "base/containers/span.h"
11 #include "base/notreached.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "net/base/net_string_util.h"
14 #include "net/ntlm/ntlm_buffer_writer.h"
15 #include "net/ntlm/ntlm_constants.h"
16 #include "third_party/boringssl/src/include/openssl/des.h"
17 #include "third_party/boringssl/src/include/openssl/hmac.h"
18 #include "third_party/boringssl/src/include/openssl/md4.h"
19 #include "third_party/boringssl/src/include/openssl/md5.h"
20 
21 namespace net::ntlm {
22 
23 namespace {
24 
25 // Takes the parsed target info in |av_pairs| and performs the following
26 // actions.
27 //
28 // 1) If a |TargetInfoAvId::kTimestamp| AvPair exists, |server_timestamp|
29 //    is set to the payload.
30 // 2) If |is_mic_enabled| is true, the existing |TargetInfoAvId::kFlags| AvPair
31 //    will have the |TargetInfoAvFlags::kMicPresent| bit set. If an existing
32 //    flags AvPair does not already exist, a new one is added with the value of
33 //    |TargetInfoAvFlags::kMicPresent|.
34 // 3) If |is_epa_enabled| is true, two new AvPair entries will be added to
35 //    |av_pairs|. The first will be of type |TargetInfoAvId::kChannelBindings|
36 //    and contains MD5(|channel_bindings|) as the payload. The second will be
37 //    of type |TargetInfoAvId::kTargetName| and contains |spn| as a little
38 //    endian UTF16 string.
39 // 4) Sets |target_info_len| to the size of |av_pairs| when serialized into
40 //    a payload.
UpdateTargetInfoAvPairs(bool is_mic_enabled,bool is_epa_enabled,const std::string & channel_bindings,const std::string & spn,std::vector<AvPair> * av_pairs,uint64_t * server_timestamp,size_t * target_info_len)41 void UpdateTargetInfoAvPairs(bool is_mic_enabled,
42                              bool is_epa_enabled,
43                              const std::string& channel_bindings,
44                              const std::string& spn,
45                              std::vector<AvPair>* av_pairs,
46                              uint64_t* server_timestamp,
47                              size_t* target_info_len) {
48   // Do a pass to update flags and calculate current length and
49   // pull out the server timestamp if it is there.
50   *server_timestamp = UINT64_MAX;
51   *target_info_len = 0;
52 
53   bool need_flags_added = is_mic_enabled;
54   for (AvPair& pair : *av_pairs) {
55     *target_info_len += pair.avlen + kAvPairHeaderLen;
56     switch (pair.avid) {
57       case TargetInfoAvId::kFlags:
58         // The parsing phase already set the payload to the |flags| field.
59         if (is_mic_enabled) {
60           pair.flags = pair.flags | TargetInfoAvFlags::kMicPresent;
61         }
62 
63         need_flags_added = false;
64         break;
65       case TargetInfoAvId::kTimestamp:
66         // The parsing phase already set the payload to the |timestamp| field.
67         *server_timestamp = pair.timestamp;
68         break;
69       case TargetInfoAvId::kEol:
70       case TargetInfoAvId::kChannelBindings:
71       case TargetInfoAvId::kTargetName:
72         // The terminator, |kEol|, should already have been removed from the
73         // end of the list and would have been rejected if it has been inside
74         // the list. Additionally |kChannelBindings| and |kTargetName| pairs
75         // would have been rejected during the initial parsing. See
76         // |NtlmBufferReader::ReadTargetInfo|.
77         NOTREACHED();
78         break;
79       default:
80         // Ignore entries we don't care about.
81         break;
82     }
83   }
84 
85   if (need_flags_added) {
86     DCHECK(is_mic_enabled);
87     AvPair flags_pair(TargetInfoAvId::kFlags, sizeof(uint32_t));
88     flags_pair.flags = TargetInfoAvFlags::kMicPresent;
89 
90     av_pairs->push_back(flags_pair);
91     *target_info_len += kAvPairHeaderLen + flags_pair.avlen;
92   }
93 
94   if (is_epa_enabled) {
95     std::vector<uint8_t> channel_bindings_hash(kChannelBindingsHashLen, 0);
96 
97     // Hash the channel bindings if they exist otherwise they remain zeros.
98     if (!channel_bindings.empty()) {
99       GenerateChannelBindingHashV2(
100           channel_bindings,
101           base::make_span<kChannelBindingsHashLen>(channel_bindings_hash));
102     }
103 
104     av_pairs->emplace_back(TargetInfoAvId::kChannelBindings,
105                            std::move(channel_bindings_hash));
106 
107     // Convert the SPN to little endian unicode.
108     std::u16string spn16 = base::UTF8ToUTF16(spn);
109     NtlmBufferWriter spn_writer(spn16.length() * 2);
110     bool spn_writer_result =
111         spn_writer.WriteUtf16String(spn16) && spn_writer.IsEndOfBuffer();
112     DCHECK(spn_writer_result);
113 
114     av_pairs->emplace_back(TargetInfoAvId::kTargetName, spn_writer.Pass());
115 
116     // Add the length of the two new AV Pairs to the total length.
117     *target_info_len +=
118         (2 * kAvPairHeaderLen) + kChannelBindingsHashLen + (spn16.length() * 2);
119   }
120 
121   // Add extra space for the terminator at the end.
122   *target_info_len += kAvPairHeaderLen;
123 }
124 
WriteUpdatedTargetInfo(const std::vector<AvPair> & av_pairs,size_t updated_target_info_len)125 std::vector<uint8_t> WriteUpdatedTargetInfo(const std::vector<AvPair>& av_pairs,
126                                             size_t updated_target_info_len) {
127   bool result = true;
128   NtlmBufferWriter writer(updated_target_info_len);
129   for (const AvPair& pair : av_pairs) {
130     result = writer.WriteAvPair(pair);
131     DCHECK(result);
132   }
133 
134   result = writer.WriteAvPairTerminator() && writer.IsEndOfBuffer();
135   DCHECK(result);
136   return writer.Pass();
137 }
138 
139 // Reads 7 bytes (56 bits) from |key_56| and writes them into 8 bytes of
140 // |key_64| with 7 bits in every byte. The least significant bits are
141 // undefined and a subsequent operation will set those bits with a parity bit.
142 // |key_56| must contain 7 bytes.
143 // |key_64| must contain 8 bytes.
Splay56To64(base::span<const uint8_t,7> key_56,base::span<uint8_t,8> key_64)144 void Splay56To64(base::span<const uint8_t, 7> key_56,
145                  base::span<uint8_t, 8> key_64) {
146   key_64[0] = key_56[0];
147   key_64[1] = key_56[0] << 7 | key_56[1] >> 1;
148   key_64[2] = key_56[1] << 6 | key_56[2] >> 2;
149   key_64[3] = key_56[2] << 5 | key_56[3] >> 3;
150   key_64[4] = key_56[3] << 4 | key_56[4] >> 4;
151   key_64[5] = key_56[4] << 3 | key_56[5] >> 5;
152   key_64[6] = key_56[5] << 2 | key_56[6] >> 6;
153   key_64[7] = key_56[6] << 1;
154 }
155 
156 }  // namespace
157 
Create3DesKeysFromNtlmHash(base::span<const uint8_t,kNtlmHashLen> ntlm_hash,base::span<uint8_t,24> keys)158 void Create3DesKeysFromNtlmHash(
159     base::span<const uint8_t, kNtlmHashLen> ntlm_hash,
160     base::span<uint8_t, 24> keys) {
161   // Put the first 112 bits from |ntlm_hash| into the first 16 bytes of
162   // |keys|.
163   Splay56To64(ntlm_hash.first<7>(), keys.first<8>());
164   Splay56To64(ntlm_hash.subspan<7, 7>(), keys.subspan<8, 8>());
165 
166   // Put the next 2x 7 bits in bytes 16 and 17 of |keys|, then
167   // the last 2 bits in byte 18, then zero pad the rest of the final key.
168   keys[16] = ntlm_hash[14];
169   keys[17] = ntlm_hash[14] << 7 | ntlm_hash[15] >> 1;
170   keys[18] = ntlm_hash[15] << 6;
171   memset(keys.data() + 19, 0, 5);
172 }
173 
GenerateNtlmHashV1(const std::u16string & password,base::span<uint8_t,kNtlmHashLen> hash)174 void GenerateNtlmHashV1(const std::u16string& password,
175                         base::span<uint8_t, kNtlmHashLen> hash) {
176   size_t length = password.length() * 2;
177   NtlmBufferWriter writer(length);
178 
179   // The writer will handle the big endian case if necessary.
180   bool result = writer.WriteUtf16String(password) && writer.IsEndOfBuffer();
181   DCHECK(result);
182 
183   MD4(writer.GetBuffer().data(), writer.GetLength(), hash.data());
184 }
185 
GenerateResponseDesl(base::span<const uint8_t,kNtlmHashLen> hash,base::span<const uint8_t,kChallengeLen> challenge,base::span<uint8_t,kResponseLenV1> response)186 void GenerateResponseDesl(base::span<const uint8_t, kNtlmHashLen> hash,
187                           base::span<const uint8_t, kChallengeLen> challenge,
188                           base::span<uint8_t, kResponseLenV1> response) {
189   constexpr size_t block_count = 3;
190   constexpr size_t block_size = sizeof(DES_cblock);
191   static_assert(kChallengeLen == block_size,
192                 "kChallengeLen must equal block_size");
193   static_assert(kResponseLenV1 == block_count * block_size,
194                 "kResponseLenV1 must equal block_count * block_size");
195 
196   const DES_cblock* challenge_block =
197       reinterpret_cast<const DES_cblock*>(challenge.data());
198   uint8_t keys[block_count * block_size];
199 
200   // Map the NTLM hash to three 8 byte DES keys, with 7 bits of the key in each
201   // byte and the least significant bit set with odd parity. Then encrypt the
202   // 8 byte challenge with each of the three keys. This produces three 8 byte
203   // encrypted blocks into |response|.
204   Create3DesKeysFromNtlmHash(hash, keys);
205   for (size_t ix = 0; ix < block_count * block_size; ix += block_size) {
206     DES_cblock* key_block = reinterpret_cast<DES_cblock*>(keys + ix);
207     DES_cblock* response_block =
208         reinterpret_cast<DES_cblock*>(response.data() + ix);
209 
210     DES_key_schedule key_schedule;
211     DES_set_odd_parity(key_block);
212     DES_set_key(key_block, &key_schedule);
213     DES_ecb_encrypt(challenge_block, response_block, &key_schedule,
214                     DES_ENCRYPT);
215   }
216 }
217 
GenerateNtlmResponseV1(const std::u16string & password,base::span<const uint8_t,kChallengeLen> server_challenge,base::span<uint8_t,kResponseLenV1> ntlm_response)218 void GenerateNtlmResponseV1(
219     const std::u16string& password,
220     base::span<const uint8_t, kChallengeLen> server_challenge,
221     base::span<uint8_t, kResponseLenV1> ntlm_response) {
222   uint8_t ntlm_hash[kNtlmHashLen];
223   GenerateNtlmHashV1(password, ntlm_hash);
224   GenerateResponseDesl(ntlm_hash, server_challenge, ntlm_response);
225 }
226 
GenerateResponsesV1(const std::u16string & password,base::span<const uint8_t,kChallengeLen> server_challenge,base::span<uint8_t,kResponseLenV1> lm_response,base::span<uint8_t,kResponseLenV1> ntlm_response)227 void GenerateResponsesV1(
228     const std::u16string& password,
229     base::span<const uint8_t, kChallengeLen> server_challenge,
230     base::span<uint8_t, kResponseLenV1> lm_response,
231     base::span<uint8_t, kResponseLenV1> ntlm_response) {
232   GenerateNtlmResponseV1(password, server_challenge, ntlm_response);
233 
234   // In NTLM v1 (with LMv1 disabled), the lm_response and ntlm_response are the
235   // same. So just copy the ntlm_response into the lm_response.
236   memcpy(lm_response.data(), ntlm_response.data(), kResponseLenV1);
237 }
238 
GenerateLMResponseV1WithSessionSecurity(base::span<const uint8_t,kChallengeLen> client_challenge,base::span<uint8_t,kResponseLenV1> lm_response)239 void GenerateLMResponseV1WithSessionSecurity(
240     base::span<const uint8_t, kChallengeLen> client_challenge,
241     base::span<uint8_t, kResponseLenV1> lm_response) {
242   // In NTLM v1 with Session Security (aka NTLM2) the lm_response is 8 bytes of
243   // client challenge and 16 bytes of zeros. (See 3.3.1)
244   memcpy(lm_response.data(), client_challenge.data(), kChallengeLen);
245   memset(lm_response.data() + kChallengeLen, 0, kResponseLenV1 - kChallengeLen);
246 }
247 
GenerateSessionHashV1WithSessionSecurity(base::span<const uint8_t,kChallengeLen> server_challenge,base::span<const uint8_t,kChallengeLen> client_challenge,base::span<uint8_t,kNtlmHashLen> session_hash)248 void GenerateSessionHashV1WithSessionSecurity(
249     base::span<const uint8_t, kChallengeLen> server_challenge,
250     base::span<const uint8_t, kChallengeLen> client_challenge,
251     base::span<uint8_t, kNtlmHashLen> session_hash) {
252   MD5_CTX ctx;
253   MD5_Init(&ctx);
254   MD5_Update(&ctx, server_challenge.data(), kChallengeLen);
255   MD5_Update(&ctx, client_challenge.data(), kChallengeLen);
256   MD5_Final(session_hash.data(), &ctx);
257 }
258 
GenerateNtlmResponseV1WithSessionSecurity(const std::u16string & password,base::span<const uint8_t,kChallengeLen> server_challenge,base::span<const uint8_t,kChallengeLen> client_challenge,base::span<uint8_t,kResponseLenV1> ntlm_response)259 void GenerateNtlmResponseV1WithSessionSecurity(
260     const std::u16string& password,
261     base::span<const uint8_t, kChallengeLen> server_challenge,
262     base::span<const uint8_t, kChallengeLen> client_challenge,
263     base::span<uint8_t, kResponseLenV1> ntlm_response) {
264   // Generate the NTLMv1 Hash.
265   uint8_t ntlm_hash[kNtlmHashLen];
266   GenerateNtlmHashV1(password, ntlm_hash);
267 
268   // Generate the NTLMv1 Session Hash.
269   uint8_t session_hash[kNtlmHashLen];
270   GenerateSessionHashV1WithSessionSecurity(server_challenge, client_challenge,
271                                            session_hash);
272 
273   GenerateResponseDesl(
274       ntlm_hash, base::make_span(session_hash).subspan<0, kChallengeLen>(),
275       ntlm_response);
276 }
277 
GenerateResponsesV1WithSessionSecurity(const std::u16string & password,base::span<const uint8_t,kChallengeLen> server_challenge,base::span<const uint8_t,kChallengeLen> client_challenge,base::span<uint8_t,kResponseLenV1> lm_response,base::span<uint8_t,kResponseLenV1> ntlm_response)278 void GenerateResponsesV1WithSessionSecurity(
279     const std::u16string& password,
280     base::span<const uint8_t, kChallengeLen> server_challenge,
281     base::span<const uint8_t, kChallengeLen> client_challenge,
282     base::span<uint8_t, kResponseLenV1> lm_response,
283     base::span<uint8_t, kResponseLenV1> ntlm_response) {
284   GenerateLMResponseV1WithSessionSecurity(client_challenge, lm_response);
285   GenerateNtlmResponseV1WithSessionSecurity(password, server_challenge,
286                                             client_challenge, ntlm_response);
287 }
288 
GenerateNtlmHashV2(const std::u16string & domain,const std::u16string & username,const std::u16string & password,base::span<uint8_t,kNtlmHashLen> v2_hash)289 void GenerateNtlmHashV2(const std::u16string& domain,
290                         const std::u16string& username,
291                         const std::u16string& password,
292                         base::span<uint8_t, kNtlmHashLen> v2_hash) {
293   // NOTE: According to [MS-NLMP] Section 3.3.2 only the username and not the
294   // domain is uppercased.
295 
296   // TODO(https://crbug.com/1051924): Using a locale-sensitive upper casing
297   // algorithm is problematic. A more predictable approach would be to only
298   // uppercase ASCII characters, so the hash does not change depending on the
299   // user's locale.
300   std::u16string upper_username;
301   bool result = ToUpper(username, &upper_username);
302   DCHECK(result);
303 
304   uint8_t v1_hash[kNtlmHashLen];
305   GenerateNtlmHashV1(password, v1_hash);
306   NtlmBufferWriter input_writer((upper_username.length() + domain.length()) *
307                                 2);
308   bool writer_result = input_writer.WriteUtf16String(upper_username) &&
309                        input_writer.WriteUtf16String(domain) &&
310                        input_writer.IsEndOfBuffer();
311   DCHECK(writer_result);
312 
313   unsigned int outlen = kNtlmHashLen;
314   uint8_t* out_hash =
315       HMAC(EVP_md5(), v1_hash, sizeof(v1_hash), input_writer.GetBuffer().data(),
316            input_writer.GetLength(), v2_hash.data(), &outlen);
317   DCHECK_EQ(v2_hash.data(), out_hash);
318   DCHECK_EQ(sizeof(v1_hash), outlen);
319 }
320 
GenerateProofInputV2(uint64_t timestamp,base::span<const uint8_t,kChallengeLen> client_challenge)321 std::vector<uint8_t> GenerateProofInputV2(
322     uint64_t timestamp,
323     base::span<const uint8_t, kChallengeLen> client_challenge) {
324   NtlmBufferWriter writer(kProofInputLenV2);
325   bool result = writer.WriteUInt16(kProofInputVersionV2) &&
326                 writer.WriteZeros(6) && writer.WriteUInt64(timestamp) &&
327                 writer.WriteBytes(client_challenge) && writer.WriteZeros(4) &&
328                 writer.IsEndOfBuffer();
329 
330   DCHECK(result);
331   return writer.Pass();
332 }
333 
GenerateNtlmProofV2(base::span<const uint8_t,kNtlmHashLen> v2_hash,base::span<const uint8_t,kChallengeLen> server_challenge,base::span<const uint8_t,kProofInputLenV2> v2_input,base::span<const uint8_t> target_info,base::span<uint8_t,kNtlmProofLenV2> v2_proof)334 void GenerateNtlmProofV2(
335     base::span<const uint8_t, kNtlmHashLen> v2_hash,
336     base::span<const uint8_t, kChallengeLen> server_challenge,
337     base::span<const uint8_t, kProofInputLenV2> v2_input,
338     base::span<const uint8_t> target_info,
339     base::span<uint8_t, kNtlmProofLenV2> v2_proof) {
340   bssl::ScopedHMAC_CTX ctx;
341   HMAC_Init_ex(ctx.get(), v2_hash.data(), kNtlmHashLen, EVP_md5(), nullptr);
342   DCHECK_EQ(kNtlmProofLenV2, HMAC_size(ctx.get()));
343   HMAC_Update(ctx.get(), server_challenge.data(), kChallengeLen);
344   HMAC_Update(ctx.get(), v2_input.data(), kProofInputLenV2);
345   HMAC_Update(ctx.get(), target_info.data(), target_info.size());
346   const uint32_t zero = 0;
347   HMAC_Update(ctx.get(), reinterpret_cast<const uint8_t*>(&zero),
348               sizeof(uint32_t));
349   HMAC_Final(ctx.get(), v2_proof.data(), nullptr);
350 }
351 
GenerateSessionBaseKeyV2(base::span<const uint8_t,kNtlmHashLen> v2_hash,base::span<const uint8_t,kNtlmProofLenV2> v2_proof,base::span<uint8_t,kSessionKeyLenV2> session_key)352 void GenerateSessionBaseKeyV2(
353     base::span<const uint8_t, kNtlmHashLen> v2_hash,
354     base::span<const uint8_t, kNtlmProofLenV2> v2_proof,
355     base::span<uint8_t, kSessionKeyLenV2> session_key) {
356   unsigned int outlen = kSessionKeyLenV2;
357   uint8_t* result =
358       HMAC(EVP_md5(), v2_hash.data(), kNtlmHashLen, v2_proof.data(),
359            kNtlmProofLenV2, session_key.data(), &outlen);
360   DCHECK_EQ(session_key.data(), result);
361   DCHECK_EQ(kSessionKeyLenV2, outlen);
362 }
363 
GenerateChannelBindingHashV2(const std::string & channel_bindings,base::span<uint8_t,kNtlmHashLen> channel_bindings_hash)364 void GenerateChannelBindingHashV2(
365     const std::string& channel_bindings,
366     base::span<uint8_t, kNtlmHashLen> channel_bindings_hash) {
367   NtlmBufferWriter writer(kEpaUnhashedStructHeaderLen);
368   bool result = writer.WriteZeros(16) &&
369                 writer.WriteUInt32(channel_bindings.length()) &&
370                 writer.IsEndOfBuffer();
371   DCHECK(result);
372 
373   MD5_CTX ctx;
374   MD5_Init(&ctx);
375   MD5_Update(&ctx, writer.GetBuffer().data(), writer.GetBuffer().size());
376   MD5_Update(&ctx, channel_bindings.data(), channel_bindings.size());
377   MD5_Final(channel_bindings_hash.data(), &ctx);
378 }
379 
GenerateMicV2(base::span<const uint8_t,kSessionKeyLenV2> session_key,base::span<const uint8_t> negotiate_msg,base::span<const uint8_t> challenge_msg,base::span<const uint8_t> authenticate_msg,base::span<uint8_t,kMicLenV2> mic)380 void GenerateMicV2(base::span<const uint8_t, kSessionKeyLenV2> session_key,
381                    base::span<const uint8_t> negotiate_msg,
382                    base::span<const uint8_t> challenge_msg,
383                    base::span<const uint8_t> authenticate_msg,
384                    base::span<uint8_t, kMicLenV2> mic) {
385   bssl::ScopedHMAC_CTX ctx;
386   HMAC_Init_ex(ctx.get(), session_key.data(), kSessionKeyLenV2, EVP_md5(),
387                nullptr);
388   DCHECK_EQ(kMicLenV2, HMAC_size(ctx.get()));
389   HMAC_Update(ctx.get(), negotiate_msg.data(), negotiate_msg.size());
390   HMAC_Update(ctx.get(), challenge_msg.data(), challenge_msg.size());
391   HMAC_Update(ctx.get(), authenticate_msg.data(), authenticate_msg.size());
392   HMAC_Final(ctx.get(), mic.data(), nullptr);
393 }
394 
GenerateUpdatedTargetInfo(bool is_mic_enabled,bool is_epa_enabled,const std::string & channel_bindings,const std::string & spn,const std::vector<AvPair> & av_pairs,uint64_t * server_timestamp)395 NET_EXPORT_PRIVATE std::vector<uint8_t> GenerateUpdatedTargetInfo(
396     bool is_mic_enabled,
397     bool is_epa_enabled,
398     const std::string& channel_bindings,
399     const std::string& spn,
400     const std::vector<AvPair>& av_pairs,
401     uint64_t* server_timestamp) {
402   size_t updated_target_info_len = 0;
403   std::vector<AvPair> updated_av_pairs(av_pairs);
404   UpdateTargetInfoAvPairs(is_mic_enabled, is_epa_enabled, channel_bindings, spn,
405                           &updated_av_pairs, server_timestamp,
406                           &updated_target_info_len);
407   return WriteUpdatedTargetInfo(updated_av_pairs, updated_target_info_len);
408 }
409 
410 }  // namespace net::ntlm
411