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