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. 4 5 #include "net/http/http_auth_gssapi_posix.h" 6 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/mock_gssapi_library_posix.h" 13 #include "testing/gtest/include/gtest/gtest.h" 14 15 namespace net { 16 17 namespace { 18 19 // gss_buffer_t helpers. 20 void ClearBuffer(gss_buffer_t dest) { 21 if (!dest) 22 return; 23 dest->length = 0; 24 delete [] reinterpret_cast<char*>(dest->value); 25 dest->value = NULL; 26 } 27 28 void SetBuffer(gss_buffer_t dest, const void* src, size_t length) { 29 if (!dest) 30 return; 31 ClearBuffer(dest); 32 if (!src) 33 return; 34 dest->length = length; 35 if (length) { 36 dest->value = new char[length]; 37 memcpy(dest->value, src, length); 38 } 39 } 40 41 void CopyBuffer(gss_buffer_t dest, const gss_buffer_t src) { 42 if (!dest) 43 return; 44 ClearBuffer(dest); 45 if (!src) 46 return; 47 SetBuffer(dest, src->value, src->length); 48 } 49 50 const char kInitialAuthResponse[] = "Mary had a little lamb"; 51 52 void EstablishInitialContext(test::MockGSSAPILibrary* library) { 53 test::GssContextMockImpl context_info( 54 "localhost", // Source name 55 "example.com", // Target name 56 23, // Lifetime 57 *CHROME_GSS_SPNEGO_MECH_OID_DESC, // Mechanism 58 0, // Context flags 59 1, // Locally initiated 60 0); // Open 61 gss_buffer_desc in_buffer = {0, NULL}; 62 gss_buffer_desc out_buffer = {arraysize(kInitialAuthResponse), 63 const_cast<char*>(kInitialAuthResponse)}; 64 library->ExpectSecurityContext( 65 "Negotiate", 66 GSS_S_CONTINUE_NEEDED, 67 0, 68 context_info, 69 in_buffer, 70 out_buffer); 71 } 72 73 } // namespace 74 75 TEST(HttpAuthGSSAPIPOSIXTest, GSSAPIStartup) { 76 // TODO(ahendrickson): Manipulate the libraries and paths to test each of the 77 // libraries we expect, and also whether or not they have the interface 78 // functions we want. 79 scoped_ptr<GSSAPILibrary> gssapi(new GSSAPISharedLibrary(std::string())); 80 DCHECK(gssapi.get()); 81 EXPECT_TRUE(gssapi.get()->Init()); 82 } 83 84 #if defined(DLOPEN_KERBEROS) 85 TEST(HttpAuthGSSAPIPOSIXTest, GSSAPILoadCustomLibrary) { 86 scoped_ptr<GSSAPILibrary> gssapi( 87 new GSSAPISharedLibrary("/this/library/does/not/exist")); 88 EXPECT_FALSE(gssapi.get()->Init()); 89 } 90 #endif // defined(DLOPEN_KERBEROS) 91 92 TEST(HttpAuthGSSAPIPOSIXTest, GSSAPICycle) { 93 scoped_ptr<test::MockGSSAPILibrary> mock_library(new test::MockGSSAPILibrary); 94 DCHECK(mock_library.get()); 95 mock_library->Init(); 96 const char kAuthResponse[] = "Mary had a little lamb"; 97 test::GssContextMockImpl context1( 98 "localhost", // Source name 99 "example.com", // Target name 100 23, // Lifetime 101 *CHROME_GSS_SPNEGO_MECH_OID_DESC, // Mechanism 102 0, // Context flags 103 1, // Locally initiated 104 0); // Open 105 test::GssContextMockImpl context2( 106 "localhost", // Source name 107 "example.com", // Target name 108 23, // Lifetime 109 *CHROME_GSS_SPNEGO_MECH_OID_DESC, // Mechanism 110 0, // Context flags 111 1, // Locally initiated 112 1); // Open 113 test::MockGSSAPILibrary::SecurityContextQuery queries[] = { 114 test::MockGSSAPILibrary::SecurityContextQuery( 115 "Negotiate", // Package name 116 GSS_S_CONTINUE_NEEDED, // Major response code 117 0, // Minor response code 118 context1, // Context 119 NULL, // Expected input token 120 kAuthResponse), // Output token 121 test::MockGSSAPILibrary::SecurityContextQuery( 122 "Negotiate", // Package name 123 GSS_S_COMPLETE, // Major response code 124 0, // Minor response code 125 context2, // Context 126 kAuthResponse, // Expected input token 127 kAuthResponse) // Output token 128 }; 129 130 for (size_t i = 0; i < arraysize(queries); ++i) { 131 mock_library->ExpectSecurityContext(queries[i].expected_package, 132 queries[i].response_code, 133 queries[i].minor_response_code, 134 queries[i].context_info, 135 queries[i].expected_input_token, 136 queries[i].output_token); 137 } 138 139 OM_uint32 major_status = 0; 140 OM_uint32 minor_status = 0; 141 gss_cred_id_t initiator_cred_handle = NULL; 142 gss_ctx_id_t context_handle = NULL; 143 gss_name_t target_name = NULL; 144 gss_OID mech_type = NULL; 145 OM_uint32 req_flags = 0; 146 OM_uint32 time_req = 25; 147 gss_channel_bindings_t input_chan_bindings = NULL; 148 gss_buffer_desc input_token = { 0, NULL }; 149 gss_OID actual_mech_type= NULL; 150 gss_buffer_desc output_token = { 0, NULL }; 151 OM_uint32 ret_flags = 0; 152 OM_uint32 time_rec = 0; 153 for (size_t i = 0; i < arraysize(queries); ++i) { 154 major_status = mock_library->init_sec_context(&minor_status, 155 initiator_cred_handle, 156 &context_handle, 157 target_name, 158 mech_type, 159 req_flags, 160 time_req, 161 input_chan_bindings, 162 &input_token, 163 &actual_mech_type, 164 &output_token, 165 &ret_flags, 166 &time_rec); 167 EXPECT_EQ(queries[i].response_code, major_status); 168 CopyBuffer(&input_token, &output_token); 169 ClearBuffer(&output_token); 170 } 171 ClearBuffer(&input_token); 172 major_status = mock_library->delete_sec_context(&minor_status, 173 &context_handle, 174 GSS_C_NO_BUFFER); 175 EXPECT_EQ(static_cast<OM_uint32>(GSS_S_COMPLETE), major_status); 176 } 177 178 TEST(HttpAuthGSSAPITest, ParseChallenge_FirstRound) { 179 // The first round should just consist of an unadorned "Negotiate" header. 180 test::MockGSSAPILibrary mock_library; 181 HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate", 182 CHROME_GSS_SPNEGO_MECH_OID_DESC); 183 std::string challenge_text = "Negotiate"; 184 HttpAuth::ChallengeTokenizer challenge(challenge_text.begin(), 185 challenge_text.end()); 186 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, 187 auth_gssapi.ParseChallenge(&challenge)); 188 } 189 190 TEST(HttpAuthGSSAPITest, ParseChallenge_TwoRounds) { 191 // The first round should just have "Negotiate", and the second round should 192 // have a valid base64 token associated with it. 193 test::MockGSSAPILibrary mock_library; 194 HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate", 195 CHROME_GSS_SPNEGO_MECH_OID_DESC); 196 std::string first_challenge_text = "Negotiate"; 197 HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(), 198 first_challenge_text.end()); 199 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, 200 auth_gssapi.ParseChallenge(&first_challenge)); 201 202 // Generate an auth token and create another thing. 203 EstablishInitialContext(&mock_library); 204 std::string auth_token; 205 EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, L"HTTP/intranet.google.com", 206 &auth_token)); 207 208 std::string second_challenge_text = "Negotiate Zm9vYmFy"; 209 HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(), 210 second_challenge_text.end()); 211 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, 212 auth_gssapi.ParseChallenge(&second_challenge)); 213 } 214 215 TEST(HttpAuthGSSAPITest, ParseChallenge_UnexpectedTokenFirstRound) { 216 // If the first round challenge has an additional authentication token, it 217 // should be treated as an invalid challenge from the server. 218 test::MockGSSAPILibrary mock_library; 219 HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate", 220 CHROME_GSS_SPNEGO_MECH_OID_DESC); 221 std::string challenge_text = "Negotiate Zm9vYmFy"; 222 HttpAuth::ChallengeTokenizer challenge(challenge_text.begin(), 223 challenge_text.end()); 224 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID, 225 auth_gssapi.ParseChallenge(&challenge)); 226 } 227 228 TEST(HttpAuthGSSAPITest, ParseChallenge_MissingTokenSecondRound) { 229 // If a later-round challenge is simply "Negotiate", it should be treated as 230 // an authentication challenge rejection from the server or proxy. 231 test::MockGSSAPILibrary mock_library; 232 HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate", 233 CHROME_GSS_SPNEGO_MECH_OID_DESC); 234 std::string first_challenge_text = "Negotiate"; 235 HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(), 236 first_challenge_text.end()); 237 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, 238 auth_gssapi.ParseChallenge(&first_challenge)); 239 240 EstablishInitialContext(&mock_library); 241 std::string auth_token; 242 EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, L"HTTP/intranet.google.com", 243 &auth_token)); 244 std::string second_challenge_text = "Negotiate"; 245 HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(), 246 second_challenge_text.end()); 247 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT, 248 auth_gssapi.ParseChallenge(&second_challenge)); 249 } 250 251 TEST(HttpAuthGSSAPITest, ParseChallenge_NonBase64EncodedToken) { 252 // If a later-round challenge has an invalid base64 encoded token, it should 253 // be treated as an invalid challenge. 254 test::MockGSSAPILibrary mock_library; 255 HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate", 256 CHROME_GSS_SPNEGO_MECH_OID_DESC); 257 std::string first_challenge_text = "Negotiate"; 258 HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(), 259 first_challenge_text.end()); 260 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, 261 auth_gssapi.ParseChallenge(&first_challenge)); 262 263 EstablishInitialContext(&mock_library); 264 std::string auth_token; 265 EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, L"HTTP/intranet.google.com", 266 &auth_token)); 267 std::string second_challenge_text = "Negotiate =happyjoy="; 268 HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(), 269 second_challenge_text.end()); 270 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID, 271 auth_gssapi.ParseChallenge(&second_challenge)); 272 } 273 274 } // namespace net 275