xref: /aosp_15_r20/external/cronet/net/http/http_auth_handler_ntlm_portable_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 #include <memory>
6 #include <string>
7 #include <string_view>
8 
9 #include "base/base64.h"
10 #include "base/containers/span.h"
11 #include "base/strings/strcat.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "build/build_config.h"
15 #include "net/base/network_anonymization_key.h"
16 #include "net/base/test_completion_callback.h"
17 #include "net/dns/mock_host_resolver.h"
18 #include "net/http/http_auth_challenge_tokenizer.h"
19 #include "net/http/http_auth_handler_ntlm.h"
20 #include "net/http/http_auth_ntlm_mechanism.h"
21 #include "net/http/http_request_info.h"
22 #include "net/http/mock_allow_http_auth_preferences.h"
23 #include "net/log/net_log_with_source.h"
24 #include "net/ntlm/ntlm.h"
25 #include "net/ntlm/ntlm_buffer_reader.h"
26 #include "net/ntlm/ntlm_buffer_writer.h"
27 #include "net/ntlm/ntlm_test_data.h"
28 #include "net/ssl/ssl_info.h"
29 #include "net/test/gtest_util.h"
30 #include "testing/gmock/include/gmock/gmock.h"
31 #include "testing/gtest/include/gtest/gtest.h"
32 #include "testing/platform_test.h"
33 #include "url/gurl.h"
34 #include "url/scheme_host_port.h"
35 
36 namespace net {
37 
38 class HttpAuthHandlerNtlmPortableTest : public PlatformTest {
39  public:
40   // Test input value defined in [MS-NLMP] Section 4.2.1.
HttpAuthHandlerNtlmPortableTest()41   HttpAuthHandlerNtlmPortableTest() {
42     http_auth_preferences_ = std::make_unique<MockAllowHttpAuthPreferences>();
43     // Disable NTLMv2 for this end to end test because it's not possible
44     // to mock all the required dependencies for NTLMv2 from here. These
45     // tests are only of the overall flow, and the detailed tests of the
46     // contents of the protocol messages are in ntlm_client_unittest.cc
47     http_auth_preferences_->set_ntlm_v2_enabled(false);
48     factory_ = std::make_unique<HttpAuthHandlerNTLM::Factory>();
49     factory_->set_http_auth_preferences(http_auth_preferences_.get());
50     creds_ = AuthCredentials(
51         base::StrCat({ntlm::test::kNtlmDomain, u"\\", ntlm::test::kUser}),
52         ntlm::test::kPassword);
53   }
54 
CreateHandler()55   int CreateHandler() {
56     url::SchemeHostPort scheme_host_port(GURL("https://foo.com"));
57     SSLInfo null_ssl_info;
58 
59     return factory_->CreateAuthHandlerFromString(
60         "NTLM", HttpAuth::AUTH_SERVER, null_ssl_info, NetworkAnonymizationKey(),
61         scheme_host_port, NetLogWithSource(), nullptr, &auth_handler_);
62   }
63 
CreateNtlmAuthHeader(base::span<const uint8_t> buffer)64   std::string CreateNtlmAuthHeader(base::span<const uint8_t> buffer) {
65     std::string output = base::Base64Encode(std::string_view(
66         reinterpret_cast<const char*>(buffer.data()), buffer.size()));
67 
68     return "NTLM " + output;
69   }
70 
71 
HandleAnotherChallenge(const std::string & challenge)72   HttpAuth::AuthorizationResult HandleAnotherChallenge(
73       const std::string& challenge) {
74     HttpAuthChallengeTokenizer tokenizer(challenge.begin(), challenge.end());
75     return GetAuthHandler()->HandleAnotherChallenge(&tokenizer);
76   }
77 
DecodeChallenge(const std::string & challenge,std::string * decoded)78   bool DecodeChallenge(const std::string& challenge, std::string* decoded) {
79     HttpAuthChallengeTokenizer tokenizer(challenge.begin(), challenge.end());
80     return base::Base64Decode(tokenizer.base64_param(), decoded);
81   }
82 
GenerateAuthToken(std::string * token)83   int GenerateAuthToken(std::string* token) {
84     TestCompletionCallback callback;
85     HttpRequestInfo request_info;
86     return callback.GetResult(GetAuthHandler()->GenerateAuthToken(
87         GetCreds(), &request_info, callback.callback(), token));
88   }
89 
ReadBytesPayload(ntlm::NtlmBufferReader * reader,base::span<uint8_t> buffer)90   bool ReadBytesPayload(ntlm::NtlmBufferReader* reader,
91                         base::span<uint8_t> buffer) {
92     ntlm::SecurityBuffer sec_buf;
93     return reader->ReadSecurityBuffer(&sec_buf) &&
94            (sec_buf.length == buffer.size()) &&
95            reader->ReadBytesFrom(sec_buf, buffer);
96   }
97 
98   // Reads bytes from a payload and assigns them to a string. This makes
99   // no assumptions about the underlying encoding.
ReadStringPayload(ntlm::NtlmBufferReader * reader,std::string * str)100   bool ReadStringPayload(ntlm::NtlmBufferReader* reader, std::string* str) {
101     ntlm::SecurityBuffer sec_buf;
102     if (!reader->ReadSecurityBuffer(&sec_buf))
103       return false;
104 
105     str->resize(sec_buf.length);
106     if (!reader->ReadBytesFrom(sec_buf, base::as_writable_byte_span(*str))) {
107       return false;
108     }
109 
110     return true;
111   }
112 
113   // Reads bytes from a payload and assigns them to a string16. This makes
114   // no assumptions about the underlying encoding. This will fail if there
115   // are an odd number of bytes in the payload.
ReadString16Payload(ntlm::NtlmBufferReader * reader,std::u16string * str)116   void ReadString16Payload(ntlm::NtlmBufferReader* reader,
117                            std::u16string* str) {
118     ntlm::SecurityBuffer sec_buf;
119     EXPECT_TRUE(reader->ReadSecurityBuffer(&sec_buf));
120     EXPECT_EQ(0, sec_buf.length % 2);
121 
122     std::vector<uint8_t> raw(sec_buf.length);
123     EXPECT_TRUE(reader->ReadBytesFrom(sec_buf, raw));
124 
125 #if defined(ARCH_CPU_BIG_ENDIAN)
126     for (size_t i = 0; i < raw.size(); i += 2) {
127       std::swap(raw[i], raw[i + 1]);
128     }
129 #endif
130 
131     str->assign(reinterpret_cast<const char16_t*>(raw.data()), raw.size() / 2);
132   }
133 
GetGenerateAuthTokenResult()134   int GetGenerateAuthTokenResult() {
135     std::string token;
136     return GenerateAuthToken(&token);
137   }
138 
GetCreds()139   AuthCredentials* GetCreds() { return &creds_; }
140 
GetAuthHandler()141   HttpAuthHandlerNTLM* GetAuthHandler() {
142     return static_cast<HttpAuthHandlerNTLM*>(auth_handler_.get());
143   }
144 
MockRandom(uint8_t * output,size_t n)145   static void MockRandom(uint8_t* output, size_t n) {
146     // This is set to 0xaa because the client challenge for testing in
147     // [MS-NLMP] Section 4.2.1 is 8 bytes of 0xaa.
148     memset(output, 0xaa, n);
149   }
150 
MockGetMSTime()151   static uint64_t MockGetMSTime() {
152     // Tue, 23 May 2017 20:13:07 +0000
153     return 131400439870000000;
154   }
155 
MockGetHostName()156   static std::string MockGetHostName() { return ntlm::test::kHostnameAscii; }
157 
158  private:
159   AuthCredentials creds_;
160   std::unique_ptr<HttpAuthHandler> auth_handler_;
161   std::unique_ptr<MockAllowHttpAuthPreferences> http_auth_preferences_;
162   std::unique_ptr<HttpAuthHandlerNTLM::Factory> factory_;
163 };
164 
TEST_F(HttpAuthHandlerNtlmPortableTest,SimpleConstruction)165 TEST_F(HttpAuthHandlerNtlmPortableTest, SimpleConstruction) {
166   ASSERT_EQ(OK, CreateHandler());
167   ASSERT_TRUE(GetAuthHandler() != nullptr);
168 }
169 
TEST_F(HttpAuthHandlerNtlmPortableTest,DoNotAllowDefaultCreds)170 TEST_F(HttpAuthHandlerNtlmPortableTest, DoNotAllowDefaultCreds) {
171   ASSERT_EQ(OK, CreateHandler());
172   ASSERT_FALSE(GetAuthHandler()->AllowsDefaultCredentials());
173 }
174 
TEST_F(HttpAuthHandlerNtlmPortableTest,AllowsExplicitCredentials)175 TEST_F(HttpAuthHandlerNtlmPortableTest, AllowsExplicitCredentials) {
176   ASSERT_EQ(OK, CreateHandler());
177   ASSERT_TRUE(GetAuthHandler()->AllowsExplicitCredentials());
178 }
179 
TEST_F(HttpAuthHandlerNtlmPortableTest,VerifyType1Message)180 TEST_F(HttpAuthHandlerNtlmPortableTest, VerifyType1Message) {
181   ASSERT_EQ(OK, CreateHandler());
182 
183   std::string token;
184   ASSERT_EQ(OK, GenerateAuthToken(&token));
185   // The type 1 message generated is always the same. The only variable
186   // part of the message is the flags and this implementation always offers
187   // the same set of flags.
188   ASSERT_EQ("NTLM TlRMTVNTUAABAAAAB4IIAAAAAAAgAAAAAAAAACAAAAA=", token);
189 }
190 
TEST_F(HttpAuthHandlerNtlmPortableTest,EmptyTokenFails)191 TEST_F(HttpAuthHandlerNtlmPortableTest, EmptyTokenFails) {
192   ASSERT_EQ(OK, CreateHandler());
193   ASSERT_EQ(OK, GetGenerateAuthTokenResult());
194 
195   // The encoded token for a type 2 message can't be empty.
196   ASSERT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT,
197             HandleAnotherChallenge("NTLM"));
198 }
199 
TEST_F(HttpAuthHandlerNtlmPortableTest,InvalidBase64Encoding)200 TEST_F(HttpAuthHandlerNtlmPortableTest, InvalidBase64Encoding) {
201   ASSERT_EQ(OK, CreateHandler());
202   ASSERT_EQ(OK, GetGenerateAuthTokenResult());
203 
204   // Token isn't valid base64.
205   ASSERT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
206             HandleAnotherChallenge("NTLM !!!!!!!!!!!!!"));
207 }
208 
TEST_F(HttpAuthHandlerNtlmPortableTest,CantChangeSchemeMidway)209 TEST_F(HttpAuthHandlerNtlmPortableTest, CantChangeSchemeMidway) {
210   ASSERT_EQ(OK, CreateHandler());
211   ASSERT_EQ(OK, GetGenerateAuthTokenResult());
212 
213   // Can't switch to a different auth scheme in the middle of the process.
214   ASSERT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID,
215             HandleAnotherChallenge("Negotiate SSdtIG5vdCBhIHJlYWwgdG9rZW4h"));
216 }
217 
TEST_F(HttpAuthHandlerNtlmPortableTest,NtlmV1AuthenticationSuccess)218 TEST_F(HttpAuthHandlerNtlmPortableTest, NtlmV1AuthenticationSuccess) {
219   HttpAuthNtlmMechanism::ScopedProcSetter proc_setter(MockGetMSTime, MockRandom,
220                                                       MockGetHostName);
221   ASSERT_EQ(OK, CreateHandler());
222   ASSERT_EQ(OK, GetGenerateAuthTokenResult());
223 
224   std::string token;
225   ASSERT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
226             HandleAnotherChallenge(
227                 CreateNtlmAuthHeader(ntlm::test::kChallengeMsgV1)));
228   ASSERT_EQ(OK, GenerateAuthToken(&token));
229 
230   // Validate the authenticate message
231   std::string decoded;
232   ASSERT_TRUE(DecodeChallenge(token, &decoded));
233   ASSERT_EQ(std::size(ntlm::test::kExpectedAuthenticateMsgSpecResponseV1),
234             decoded.size());
235   ASSERT_EQ(0, memcmp(decoded.data(),
236                       ntlm::test::kExpectedAuthenticateMsgSpecResponseV1,
237                       decoded.size()));
238 }
239 
240 }  // namespace net
241