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