1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/http/http_auth_gssapi_posix.h"
7 #include "base/basictypes.h"
8 #include "base/logging.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/native_library.h"
11 #include "net/base/net_errors.h"
12 #include "net/http/http_auth_challenge_tokenizer.h"
13 #include "net/http/mock_gssapi_library_posix.h"
14 #include "testing/gtest/include/gtest/gtest.h"
20 // gss_buffer_t helpers.
21 void ClearBuffer(gss_buffer_t dest
) {
25 delete [] reinterpret_cast<char*>(dest
->value
);
29 void SetBuffer(gss_buffer_t dest
, const void* src
, size_t length
) {
35 dest
->length
= length
;
37 dest
->value
= new char[length
];
38 memcpy(dest
->value
, src
, length
);
42 void CopyBuffer(gss_buffer_t dest
, const gss_buffer_t src
) {
48 SetBuffer(dest
, src
->value
, src
->length
);
51 const char kInitialAuthResponse
[] = "Mary had a little lamb";
53 void EstablishInitialContext(test::MockGSSAPILibrary
* library
) {
54 test::GssContextMockImpl
context_info(
55 "localhost", // Source name
56 "example.com", // Target name
58 *CHROME_GSS_SPNEGO_MECH_OID_DESC
, // Mechanism
60 1, // Locally initiated
62 gss_buffer_desc in_buffer
= {0, NULL
};
63 gss_buffer_desc out_buffer
= {arraysize(kInitialAuthResponse
),
64 const_cast<char*>(kInitialAuthResponse
)};
65 library
->ExpectSecurityContext(
67 GSS_S_CONTINUE_NEEDED
,
74 void UnexpectedCallback(int result
) {
75 // At present getting tokens from gssapi is fully synchronous, so the callback
76 // should never be called.
82 TEST(HttpAuthGSSAPIPOSIXTest
, GSSAPIStartup
) {
83 // TODO(ahendrickson): Manipulate the libraries and paths to test each of the
84 // libraries we expect, and also whether or not they have the interface
86 scoped_ptr
<GSSAPILibrary
> gssapi(new GSSAPISharedLibrary(std::string()));
88 EXPECT_TRUE(gssapi
.get()->Init());
91 #if defined(DLOPEN_KERBEROS)
92 TEST(HttpAuthGSSAPIPOSIXTest
, GSSAPILoadCustomLibrary
) {
93 scoped_ptr
<GSSAPILibrary
> gssapi(
94 new GSSAPISharedLibrary("/this/library/does/not/exist"));
95 EXPECT_FALSE(gssapi
.get()->Init());
97 #endif // defined(DLOPEN_KERBEROS)
99 TEST(HttpAuthGSSAPIPOSIXTest
, GSSAPICycle
) {
100 scoped_ptr
<test::MockGSSAPILibrary
> mock_library(new test::MockGSSAPILibrary
);
101 DCHECK(mock_library
.get());
102 mock_library
->Init();
103 const char kAuthResponse
[] = "Mary had a little lamb";
104 test::GssContextMockImpl
context1(
105 "localhost", // Source name
106 "example.com", // Target name
108 *CHROME_GSS_SPNEGO_MECH_OID_DESC
, // Mechanism
110 1, // Locally initiated
112 test::GssContextMockImpl
context2(
113 "localhost", // Source name
114 "example.com", // Target name
116 *CHROME_GSS_SPNEGO_MECH_OID_DESC
, // Mechanism
118 1, // Locally initiated
120 test::MockGSSAPILibrary::SecurityContextQuery queries
[] = {
121 test::MockGSSAPILibrary::SecurityContextQuery(
122 "Negotiate", // Package name
123 GSS_S_CONTINUE_NEEDED
, // Major response code
124 0, // Minor response code
126 NULL
, // Expected input token
127 kAuthResponse
), // Output token
128 test::MockGSSAPILibrary::SecurityContextQuery(
129 "Negotiate", // Package name
130 GSS_S_COMPLETE
, // Major response code
131 0, // Minor response code
133 kAuthResponse
, // Expected input token
134 kAuthResponse
) // Output token
137 for (size_t i
= 0; i
< arraysize(queries
); ++i
) {
138 mock_library
->ExpectSecurityContext(queries
[i
].expected_package
,
139 queries
[i
].response_code
,
140 queries
[i
].minor_response_code
,
141 queries
[i
].context_info
,
142 queries
[i
].expected_input_token
,
143 queries
[i
].output_token
);
146 OM_uint32 major_status
= 0;
147 OM_uint32 minor_status
= 0;
148 gss_cred_id_t initiator_cred_handle
= NULL
;
149 gss_ctx_id_t context_handle
= NULL
;
150 gss_name_t target_name
= NULL
;
151 gss_OID mech_type
= NULL
;
152 OM_uint32 req_flags
= 0;
153 OM_uint32 time_req
= 25;
154 gss_channel_bindings_t input_chan_bindings
= NULL
;
155 gss_buffer_desc input_token
= { 0, NULL
};
156 gss_OID actual_mech_type
= NULL
;
157 gss_buffer_desc output_token
= { 0, NULL
};
158 OM_uint32 ret_flags
= 0;
159 OM_uint32 time_rec
= 0;
160 for (size_t i
= 0; i
< arraysize(queries
); ++i
) {
161 major_status
= mock_library
->init_sec_context(&minor_status
,
162 initiator_cred_handle
,
174 EXPECT_EQ(queries
[i
].response_code
, major_status
);
175 CopyBuffer(&input_token
, &output_token
);
176 ClearBuffer(&output_token
);
178 ClearBuffer(&input_token
);
179 major_status
= mock_library
->delete_sec_context(&minor_status
,
182 EXPECT_EQ(static_cast<OM_uint32
>(GSS_S_COMPLETE
), major_status
);
185 TEST(HttpAuthGSSAPITest
, ParseChallenge_FirstRound
) {
186 // The first round should just consist of an unadorned "Negotiate" header.
187 test::MockGSSAPILibrary mock_library
;
188 HttpAuthGSSAPI
auth_gssapi(&mock_library
, "Negotiate",
189 CHROME_GSS_SPNEGO_MECH_OID_DESC
);
190 std::string challenge_text
= "Negotiate";
191 HttpAuthChallengeTokenizer
challenge(challenge_text
.begin(),
192 challenge_text
.end());
193 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT
,
194 auth_gssapi
.ParseChallenge(&challenge
));
197 TEST(HttpAuthGSSAPITest
, ParseChallenge_TwoRounds
) {
198 // The first round should just have "Negotiate", and the second round should
199 // have a valid base64 token associated with it.
200 test::MockGSSAPILibrary mock_library
;
201 HttpAuthGSSAPI
auth_gssapi(&mock_library
, "Negotiate",
202 CHROME_GSS_SPNEGO_MECH_OID_DESC
);
203 std::string first_challenge_text
= "Negotiate";
204 HttpAuthChallengeTokenizer
first_challenge(first_challenge_text
.begin(),
205 first_challenge_text
.end());
206 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT
,
207 auth_gssapi
.ParseChallenge(&first_challenge
));
209 // Generate an auth token and create another thing.
210 EstablishInitialContext(&mock_library
);
211 std::string auth_token
;
212 EXPECT_EQ(OK
, auth_gssapi
.GenerateAuthToken(NULL
, "HTTP/intranet.google.com",
214 base::Bind(&UnexpectedCallback
)));
216 std::string second_challenge_text
= "Negotiate Zm9vYmFy";
217 HttpAuthChallengeTokenizer
second_challenge(second_challenge_text
.begin(),
218 second_challenge_text
.end());
219 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT
,
220 auth_gssapi
.ParseChallenge(&second_challenge
));
223 TEST(HttpAuthGSSAPITest
, ParseChallenge_UnexpectedTokenFirstRound
) {
224 // If the first round challenge has an additional authentication token, it
225 // should be treated as an invalid challenge from the server.
226 test::MockGSSAPILibrary mock_library
;
227 HttpAuthGSSAPI
auth_gssapi(&mock_library
, "Negotiate",
228 CHROME_GSS_SPNEGO_MECH_OID_DESC
);
229 std::string challenge_text
= "Negotiate Zm9vYmFy";
230 HttpAuthChallengeTokenizer
challenge(challenge_text
.begin(),
231 challenge_text
.end());
232 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID
,
233 auth_gssapi
.ParseChallenge(&challenge
));
236 TEST(HttpAuthGSSAPITest
, ParseChallenge_MissingTokenSecondRound
) {
237 // If a later-round challenge is simply "Negotiate", it should be treated as
238 // an authentication challenge rejection from the server or proxy.
239 test::MockGSSAPILibrary mock_library
;
240 HttpAuthGSSAPI
auth_gssapi(&mock_library
, "Negotiate",
241 CHROME_GSS_SPNEGO_MECH_OID_DESC
);
242 std::string first_challenge_text
= "Negotiate";
243 HttpAuthChallengeTokenizer
first_challenge(first_challenge_text
.begin(),
244 first_challenge_text
.end());
245 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT
,
246 auth_gssapi
.ParseChallenge(&first_challenge
));
248 EstablishInitialContext(&mock_library
);
249 std::string auth_token
;
250 EXPECT_EQ(OK
, auth_gssapi
.GenerateAuthToken(NULL
, "HTTP/intranet.google.com",
252 base::Bind(&UnexpectedCallback
)));
253 std::string second_challenge_text
= "Negotiate";
254 HttpAuthChallengeTokenizer
second_challenge(second_challenge_text
.begin(),
255 second_challenge_text
.end());
256 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT
,
257 auth_gssapi
.ParseChallenge(&second_challenge
));
260 TEST(HttpAuthGSSAPITest
, ParseChallenge_NonBase64EncodedToken
) {
261 // If a later-round challenge has an invalid base64 encoded token, it should
262 // be treated as an invalid challenge.
263 test::MockGSSAPILibrary mock_library
;
264 HttpAuthGSSAPI
auth_gssapi(&mock_library
, "Negotiate",
265 CHROME_GSS_SPNEGO_MECH_OID_DESC
);
266 std::string first_challenge_text
= "Negotiate";
267 HttpAuthChallengeTokenizer
first_challenge(first_challenge_text
.begin(),
268 first_challenge_text
.end());
269 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT
,
270 auth_gssapi
.ParseChallenge(&first_challenge
));
272 EstablishInitialContext(&mock_library
);
273 std::string auth_token
;
274 EXPECT_EQ(OK
, auth_gssapi
.GenerateAuthToken(NULL
, "HTTP/intranet.google.com",
276 base::Bind(&UnexpectedCallback
)));
277 std::string second_challenge_text
= "Negotiate =happyjoy=";
278 HttpAuthChallengeTokenizer
second_challenge(second_challenge_text
.begin(),
279 second_challenge_text
.end());
280 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID
,
281 auth_gssapi
.ParseChallenge(&second_challenge
));