xref: /aosp_15_r20/external/cronet/net/ntlm/ntlm_unittest.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 // Tests on exact results from cryptographic operations are based on test data
6 // provided in [MS-NLMP] Version 28.0 [1] Section 4.2.
7 //
8 // Additional sanity checks on the low level hashing operations test for
9 // properties of the outputs, such as whether the hashes change, whether they
10 // should be zeroed out, or whether they should be the same or different.
11 //
12 // [1] https://msdn.microsoft.com/en-us/library/cc236621.aspx
13 
14 #include "net/ntlm/ntlm.h"
15 
16 #include <iterator>
17 #include <string>
18 
19 #include "base/ranges/algorithm.h"
20 #include "base/strings/utf_string_conversions.h"
21 #include "net/ntlm/ntlm_test_data.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 
24 namespace net::ntlm {
25 
26 namespace {
27 
MakeDomainAvPair()28 AvPair MakeDomainAvPair() {
29   return AvPair(TargetInfoAvId::kDomainName,
30                 std::vector<uint8_t>{std::begin(test::kNtlmDomainRaw),
31                                      std::end(test::kNtlmDomainRaw)});
32 }
33 
MakeServerAvPair()34 AvPair MakeServerAvPair() {
35   return AvPair(TargetInfoAvId::kServerName,
36                 std::vector<uint8_t>{std::begin(test::kServerRaw),
37                                      std::end(test::kServerRaw)});
38 }
39 
40 // Clear the least significant bit in each byte.
ClearLsb(base::span<uint8_t> data)41 void ClearLsb(base::span<uint8_t> data) {
42   for (uint8_t& byte : data) {
43     byte &= ~1;
44   }
45 }
46 
47 }  // namespace
48 
TEST(NtlmTest,MapHashToDesKeysAllOnes)49 TEST(NtlmTest, MapHashToDesKeysAllOnes) {
50   // Test mapping an NTLM hash with all 1 bits.
51   const uint8_t hash[16] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
52                             0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
53   const uint8_t expected[24] = {0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
54                                 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
55                                 0xfe, 0xfe, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00};
56 
57   uint8_t result[24];
58   Create3DesKeysFromNtlmHash(hash, result);
59   // The least significant bit in result from |Create3DesKeysFromNtlmHash|
60   // is undefined, so clear it to do memcmp.
61   ClearLsb(result);
62 
63   EXPECT_TRUE(base::ranges::equal(expected, result));
64 }
65 
TEST(NtlmTest,MapHashToDesKeysAllZeros)66 TEST(NtlmTest, MapHashToDesKeysAllZeros) {
67   // Test mapping an NTLM hash with all 0 bits.
68   const uint8_t hash[16] = {0x00};
69   const uint8_t expected[24] = {0x00};
70 
71   uint8_t result[24];
72   Create3DesKeysFromNtlmHash(hash, result);
73   // The least significant bit in result from |Create3DesKeysFromNtlmHash|
74   // is undefined, so clear it to do memcmp.
75   ClearLsb(result);
76 
77   EXPECT_TRUE(base::ranges::equal(expected, result));
78 }
79 
TEST(NtlmTest,MapHashToDesKeysAlternatingBits)80 TEST(NtlmTest, MapHashToDesKeysAlternatingBits) {
81   // Test mapping an NTLM hash with alternating 0 and 1 bits.
82   const uint8_t hash[16] = {0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
83                             0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa};
84   const uint8_t expected[24] = {0xaa, 0x54, 0xaa, 0x54, 0xaa, 0x54, 0xaa, 0x54,
85                                 0xaa, 0x54, 0xaa, 0x54, 0xaa, 0x54, 0xaa, 0x54,
86                                 0xaa, 0x54, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00};
87 
88   uint8_t result[24];
89   Create3DesKeysFromNtlmHash(hash, result);
90   // The least significant bit in result from |Create3DesKeysFromNtlmHash|
91   // is undefined, so clear it to do memcmp.
92   ClearLsb(result);
93 
94   EXPECT_TRUE(base::ranges::equal(expected, result));
95 }
96 
TEST(NtlmTest,GenerateNtlmHashV1PasswordSpecTests)97 TEST(NtlmTest, GenerateNtlmHashV1PasswordSpecTests) {
98   uint8_t hash[kNtlmHashLen];
99   GenerateNtlmHashV1(test::kPassword, hash);
100   ASSERT_EQ(0, memcmp(hash, test::kExpectedNtlmHashV1, kNtlmHashLen));
101 }
102 
TEST(NtlmTest,GenerateNtlmHashV1PasswordChangesHash)103 TEST(NtlmTest, GenerateNtlmHashV1PasswordChangesHash) {
104   std::u16string password1 = u"pwd01";
105   std::u16string password2 = u"pwd02";
106   uint8_t hash1[kNtlmHashLen];
107   uint8_t hash2[kNtlmHashLen];
108 
109   GenerateNtlmHashV1(password1, hash1);
110   GenerateNtlmHashV1(password2, hash2);
111 
112   // Verify that the hash is different with a different password.
113   ASSERT_NE(0, memcmp(hash1, hash2, kNtlmHashLen));
114 }
115 
TEST(NtlmTest,GenerateResponsesV1SpecTests)116 TEST(NtlmTest, GenerateResponsesV1SpecTests) {
117   uint8_t lm_response[kResponseLenV1];
118   uint8_t ntlm_response[kResponseLenV1];
119   GenerateResponsesV1(test::kPassword, test::kServerChallenge, lm_response,
120                       ntlm_response);
121 
122   ASSERT_EQ(
123       0, memcmp(test::kExpectedNtlmResponseV1, ntlm_response, kResponseLenV1));
124 
125   // This implementation never sends an LMv1 response (spec equivalent of the
126   // client variable NoLMResponseNTLMv1 being false) so the LM response is
127   // equal to the NTLM response when
128   // NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY is not negotiated. See
129   // [MS-NLMP] Section 3.3.1.
130   ASSERT_EQ(0,
131             memcmp(test::kExpectedNtlmResponseV1, lm_response, kResponseLenV1));
132 }
133 
TEST(NtlmTest,GenerateResponsesV1WithSessionSecuritySpecTests)134 TEST(NtlmTest, GenerateResponsesV1WithSessionSecuritySpecTests) {
135   uint8_t lm_response[kResponseLenV1];
136   uint8_t ntlm_response[kResponseLenV1];
137   GenerateResponsesV1WithSessionSecurity(
138       test::kPassword, test::kServerChallenge, test::kClientChallenge,
139       lm_response, ntlm_response);
140 
141   ASSERT_EQ(0, memcmp(test::kExpectedLmResponseWithV1SS, lm_response,
142                       kResponseLenV1));
143   ASSERT_EQ(0, memcmp(test::kExpectedNtlmResponseWithV1SS, ntlm_response,
144                       kResponseLenV1));
145 }
146 
TEST(NtlmTest,GenerateResponsesV1WithSessionSecurityClientChallengeUsed)147 TEST(NtlmTest, GenerateResponsesV1WithSessionSecurityClientChallengeUsed) {
148   uint8_t lm_response1[kResponseLenV1];
149   uint8_t lm_response2[kResponseLenV1];
150   uint8_t ntlm_response1[kResponseLenV1];
151   uint8_t ntlm_response2[kResponseLenV1];
152   uint8_t client_challenge1[kChallengeLen];
153   uint8_t client_challenge2[kChallengeLen];
154 
155   memset(client_challenge1, 0x01, kChallengeLen);
156   memset(client_challenge2, 0x02, kChallengeLen);
157 
158   GenerateResponsesV1WithSessionSecurity(
159       test::kPassword, test::kServerChallenge, client_challenge1, lm_response1,
160       ntlm_response1);
161   GenerateResponsesV1WithSessionSecurity(
162       test::kPassword, test::kServerChallenge, client_challenge2, lm_response2,
163       ntlm_response2);
164 
165   // The point of session security is that the client can introduce some
166   // randomness, so verify different client_challenge gives a different result.
167   ASSERT_NE(0, memcmp(lm_response1, lm_response2, kResponseLenV1));
168   ASSERT_NE(0, memcmp(ntlm_response1, ntlm_response2, kResponseLenV1));
169 
170   // With session security the lm and ntlm hash should be different.
171   ASSERT_NE(0, memcmp(lm_response1, ntlm_response1, kResponseLenV1));
172   ASSERT_NE(0, memcmp(lm_response2, ntlm_response2, kResponseLenV1));
173 }
174 
TEST(NtlmTest,GenerateResponsesV1WithSessionSecurityVerifySSUsed)175 TEST(NtlmTest, GenerateResponsesV1WithSessionSecurityVerifySSUsed) {
176   uint8_t lm_response1[kResponseLenV1];
177   uint8_t lm_response2[kResponseLenV1];
178   uint8_t ntlm_response1[kResponseLenV1];
179   uint8_t ntlm_response2[kResponseLenV1];
180 
181   GenerateResponsesV1WithSessionSecurity(
182       test::kPassword, test::kServerChallenge, test::kClientChallenge,
183       lm_response1, ntlm_response1);
184   GenerateResponsesV1(test::kPassword, test::kServerChallenge, lm_response2,
185                       ntlm_response2);
186 
187   // Verify that the responses with session security are not the
188   // same as without it.
189   ASSERT_NE(0, memcmp(lm_response1, lm_response2, kResponseLenV1));
190   ASSERT_NE(0, memcmp(ntlm_response1, ntlm_response2, kResponseLenV1));
191 }
192 
193 // ------------------------------------------------
194 // NTLM V2 specific tests.
195 // ------------------------------------------------
196 
TEST(NtlmTest,GenerateNtlmHashV2SpecTests)197 TEST(NtlmTest, GenerateNtlmHashV2SpecTests) {
198   uint8_t hash[kNtlmHashLen];
199   GenerateNtlmHashV2(test::kNtlmDomain, test::kUser, test::kPassword, hash);
200   ASSERT_EQ(0, memcmp(hash, test::kExpectedNtlmHashV2, kNtlmHashLen));
201 }
202 
TEST(NtlmTest,GenerateProofInputV2SpecTests)203 TEST(NtlmTest, GenerateProofInputV2SpecTests) {
204   std::vector<uint8_t> proof_input;
205   proof_input =
206       GenerateProofInputV2(test::kServerTimestamp, test::kClientChallenge);
207   ASSERT_EQ(kProofInputLenV2, proof_input.size());
208 
209   // |GenerateProofInputV2| generates the first |kProofInputLenV2| bytes of
210   // what [MS-NLMP] calls "temp".
211   ASSERT_EQ(0, memcmp(test::kExpectedTempFromSpecV2, proof_input.data(),
212                       proof_input.size()));
213 }
214 
TEST(NtlmTest,GenerateNtlmProofV2SpecTests)215 TEST(NtlmTest, GenerateNtlmProofV2SpecTests) {
216   // Only the first |kProofInputLenV2| bytes of |test::kExpectedTempFromSpecV2|
217   // are read and this is equivalent to the output of |GenerateProofInputV2|.
218   // See |GenerateProofInputV2SpecTests| for validation.
219   uint8_t v2_proof[kNtlmProofLenV2];
220   GenerateNtlmProofV2(test::kExpectedNtlmHashV2, test::kServerChallenge,
221                       base::make_span(test::kExpectedTempFromSpecV2)
222                           .subspan<0, kProofInputLenV2>(),
223                       test::kExpectedTargetInfoFromSpecV2, v2_proof);
224 
225   ASSERT_EQ(0,
226             memcmp(test::kExpectedProofFromSpecV2, v2_proof, kNtlmProofLenV2));
227 }
228 
TEST(NtlmTest,GenerateSessionBaseKeyV2SpecTests)229 TEST(NtlmTest, GenerateSessionBaseKeyV2SpecTests) {
230   // Generate the session base key.
231   uint8_t session_base_key[kSessionKeyLenV2];
232   GenerateSessionBaseKeyV2(test::kExpectedNtlmHashV2,
233                            test::kExpectedProofFromSpecV2, session_base_key);
234 
235   // Verify the session base key.
236   ASSERT_EQ(0, memcmp(test::kExpectedSessionBaseKeyFromSpecV2, session_base_key,
237                       kSessionKeyLenV2));
238 }
239 
TEST(NtlmTest,GenerateSessionBaseKeyWithClientTimestampV2SpecTests)240 TEST(NtlmTest, GenerateSessionBaseKeyWithClientTimestampV2SpecTests) {
241   // Generate the session base key.
242   uint8_t session_base_key[kSessionKeyLenV2];
243   GenerateSessionBaseKeyV2(
244       test::kExpectedNtlmHashV2,
245       test::kExpectedProofSpecResponseWithClientTimestampV2, session_base_key);
246 
247   // Verify the session base key.
248   ASSERT_EQ(0, memcmp(test::kExpectedSessionBaseKeyWithClientTimestampV2,
249                       session_base_key, kSessionKeyLenV2));
250 }
251 
TEST(NtlmTest,GenerateChannelBindingHashV2SpecTests)252 TEST(NtlmTest, GenerateChannelBindingHashV2SpecTests) {
253   uint8_t v2_channel_binding_hash[kChannelBindingsHashLen];
254   GenerateChannelBindingHashV2(
255       reinterpret_cast<const char*>(test::kChannelBindings),
256       v2_channel_binding_hash);
257 
258   ASSERT_EQ(0, memcmp(test::kExpectedChannelBindingHashV2,
259                       v2_channel_binding_hash, kChannelBindingsHashLen));
260 }
261 
TEST(NtlmTest,GenerateMicV2Simple)262 TEST(NtlmTest, GenerateMicV2Simple) {
263   // The MIC is defined as HMAC_MD5(session_base_key, CONCAT(a, b, c)) where
264   // a, b, c are the negotiate, challenge and authenticate messages
265   // respectively.
266   //
267   // This compares a simple set of inputs to a precalculated result.
268   const std::vector<uint8_t> a{0x44, 0x44, 0x44, 0x44};
269   const std::vector<uint8_t> b{0x66, 0x66, 0x66, 0x66, 0x66, 0x66};
270   const std::vector<uint8_t> c{0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88};
271 
272   // expected_mic = HMAC_MD5(
273   //          key=8de40ccadbc14a82f15cb0ad0de95ca3,
274   //          input=444444446666666666668888888888888888)
275   uint8_t expected_mic[kMicLenV2] = {0x71, 0xfe, 0xef, 0xd7, 0x76, 0xd4,
276                                      0x42, 0xa8, 0x5f, 0x6e, 0x18, 0x0a,
277                                      0x6b, 0x02, 0x47, 0x20};
278 
279   uint8_t mic[kMicLenV2];
280   GenerateMicV2(test::kExpectedSessionBaseKeyFromSpecV2, a, b, c, mic);
281   ASSERT_EQ(0, memcmp(expected_mic, mic, kMicLenV2));
282 }
283 
TEST(NtlmTest,GenerateMicSpecResponseV2)284 TEST(NtlmTest, GenerateMicSpecResponseV2) {
285   std::vector<uint8_t> authenticate_msg(
286       std::begin(test::kExpectedAuthenticateMsgSpecResponseV2),
287       std::end(test::kExpectedAuthenticateMsgSpecResponseV2));
288   memset(&authenticate_msg[kMicOffsetV2], 0x00, kMicLenV2);
289 
290   uint8_t mic[kMicLenV2];
291   GenerateMicV2(test::kExpectedSessionBaseKeyWithClientTimestampV2,
292                 test::kExpectedNegotiateMsg, test::kChallengeMsgFromSpecV2,
293                 authenticate_msg, mic);
294   ASSERT_EQ(0, memcmp(test::kExpectedMicV2, mic, kMicLenV2));
295 }
296 
TEST(NtlmTest,GenerateUpdatedTargetInfo)297 TEST(NtlmTest, GenerateUpdatedTargetInfo) {
298   // This constructs a std::vector<AvPair> that corresponds to the test input
299   // values in [MS-NLMP] Section 4.2.4.
300   std::vector<AvPair> server_av_pairs;
301   server_av_pairs.push_back(MakeDomainAvPair());
302   server_av_pairs.push_back(MakeServerAvPair());
303 
304   uint64_t server_timestamp = UINT64_MAX;
305   std::vector<uint8_t> updated_target_info = GenerateUpdatedTargetInfo(
306       true, true, reinterpret_cast<const char*>(test::kChannelBindings),
307       test::kNtlmSpn, server_av_pairs, &server_timestamp);
308 
309   // With MIC and EPA enabled 3 additional AvPairs will be added.
310   // 1) A flags AVPair with the MIC_PRESENT bit set.
311   // 2) A channel bindings AVPair containing the channel bindings hash.
312   // 3) A target name AVPair containing the SPN of the server.
313   ASSERT_EQ(std::size(test::kExpectedTargetInfoSpecResponseV2),
314             updated_target_info.size());
315   ASSERT_EQ(0, memcmp(test::kExpectedTargetInfoSpecResponseV2,
316                       updated_target_info.data(), updated_target_info.size()));
317 }
318 
TEST(NtlmTest,GenerateUpdatedTargetInfoNoEpaOrMic)319 TEST(NtlmTest, GenerateUpdatedTargetInfoNoEpaOrMic) {
320   // This constructs a std::vector<AvPair> that corresponds to the test input
321   // values in [MS-NLMP] Section 4.2.4.
322   std::vector<AvPair> server_av_pairs;
323   server_av_pairs.push_back(MakeDomainAvPair());
324   server_av_pairs.push_back(MakeServerAvPair());
325 
326   uint64_t server_timestamp = UINT64_MAX;
327 
328   // When both EPA and MIC are false the target info does not get modified by
329   // the client.
330   std::vector<uint8_t> updated_target_info = GenerateUpdatedTargetInfo(
331       false, false, reinterpret_cast<const char*>(test::kChannelBindings),
332       test::kNtlmSpn, server_av_pairs, &server_timestamp);
333   ASSERT_EQ(std::size(test::kExpectedTargetInfoFromSpecV2),
334             updated_target_info.size());
335   ASSERT_EQ(0, memcmp(test::kExpectedTargetInfoFromSpecV2,
336                       updated_target_info.data(), updated_target_info.size()));
337 }
338 
TEST(NtlmTest,GenerateUpdatedTargetInfoWithServerTimestamp)339 TEST(NtlmTest, GenerateUpdatedTargetInfoWithServerTimestamp) {
340   // This constructs a std::vector<AvPair> that corresponds to the test input
341   // values in [MS-NLMP] Section 4.2.4 with an additional server timestamp.
342   std::vector<AvPair> server_av_pairs;
343   server_av_pairs.push_back(MakeDomainAvPair());
344   server_av_pairs.push_back(MakeServerAvPair());
345 
346   // Set the timestamp to |test::kServerTimestamp| and the buffer to all zeros.
347   AvPair pair(TargetInfoAvId::kTimestamp,
348               std::vector<uint8_t>(sizeof(uint64_t), 0));
349   pair.timestamp = test::kServerTimestamp;
350   server_av_pairs.push_back(std::move(pair));
351 
352   uint64_t server_timestamp = UINT64_MAX;
353   // When both EPA and MIC are false the target info does not get modified by
354   // the client.
355   std::vector<uint8_t> updated_target_info = GenerateUpdatedTargetInfo(
356       false, false, reinterpret_cast<const char*>(test::kChannelBindings),
357       test::kNtlmSpn, server_av_pairs, &server_timestamp);
358   // Verify that the server timestamp was read from the target info.
359   ASSERT_EQ(test::kServerTimestamp, server_timestamp);
360   ASSERT_EQ(std::size(test::kExpectedTargetInfoFromSpecPlusServerTimestampV2),
361             updated_target_info.size());
362   ASSERT_EQ(0, memcmp(test::kExpectedTargetInfoFromSpecPlusServerTimestampV2,
363                       updated_target_info.data(), updated_target_info.size()));
364 }
365 
TEST(NtlmTest,GenerateUpdatedTargetInfoWhenServerSendsNoTargetInfo)366 TEST(NtlmTest, GenerateUpdatedTargetInfoWhenServerSendsNoTargetInfo) {
367   // In some older implementations the server supports NTLMv2 but does not
368   // send target info. This manifests as an empty list of AvPairs.
369   std::vector<AvPair> server_av_pairs;
370 
371   uint64_t server_timestamp = UINT64_MAX;
372   std::vector<uint8_t> updated_target_info = GenerateUpdatedTargetInfo(
373       true, true, reinterpret_cast<const char*>(test::kChannelBindings),
374       test::kNtlmSpn, server_av_pairs, &server_timestamp);
375 
376   // With MIC and EPA enabled 3 additional AvPairs will be added.
377   // 1) A flags AVPair with the MIC_PRESENT bit set.
378   // 2) A channel bindings AVPair containing the channel bindings hash.
379   // 3) A target name AVPair containing the SPN of the server.
380   //
381   // Compared to the spec example in |GenerateUpdatedTargetInfo| the result
382   // is the same but with the first 32 bytes (which were the Domain and
383   // Server pairs) not present.
384   const size_t kMissingServerPairsLength = 32;
385 
386   ASSERT_EQ(std::size(test::kExpectedTargetInfoSpecResponseV2) -
387                 kMissingServerPairsLength,
388             updated_target_info.size());
389   ASSERT_EQ(0, memcmp(test::kExpectedTargetInfoSpecResponseV2 +
390                           kMissingServerPairsLength,
391                       updated_target_info.data(), updated_target_info.size()));
392 }
393 
TEST(NtlmTest,GenerateNtlmProofV2)394 TEST(NtlmTest, GenerateNtlmProofV2) {
395   uint8_t proof[kNtlmProofLenV2];
396 
397   GenerateNtlmProofV2(test::kExpectedNtlmHashV2, test::kServerChallenge,
398                       base::make_span(test::kExpectedTempFromSpecV2)
399                           .subspan<0, kProofInputLenV2>(),
400                       test::kExpectedTargetInfoSpecResponseV2, proof);
401   ASSERT_EQ(0,
402             memcmp(test::kExpectedProofSpecResponseV2, proof, kNtlmProofLenV2));
403 }
404 
TEST(NtlmTest,GenerateNtlmProofWithClientTimestampV2)405 TEST(NtlmTest, GenerateNtlmProofWithClientTimestampV2) {
406   uint8_t proof[kNtlmProofLenV2];
407 
408   // Since the test data for "temp" in the spec does not include the client
409   // timestamp, a separate proof test value must be validated for use in full
410   // message validation.
411   GenerateNtlmProofV2(test::kExpectedNtlmHashV2, test::kServerChallenge,
412                       base::make_span(test::kExpectedTempWithClientTimestampV2)
413                           .subspan<0, kProofInputLenV2>(),
414                       test::kExpectedTargetInfoSpecResponseV2, proof);
415   ASSERT_EQ(0, memcmp(test::kExpectedProofSpecResponseWithClientTimestampV2,
416                       proof, kNtlmProofLenV2));
417 }
418 
419 }  // namespace net::ntlm
420