1 // Copyright (c) 2011 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 *GSS_C_NT_HOSTBASED_SERVICE, // 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("")); 80 DCHECK(gssapi.get()); 81 DCHECK(gssapi.get()->Init()); 82 } 83 84 TEST(HttpAuthGSSAPIPOSIXTest, GSSAPILoadCustomLibrary) { 85 scoped_ptr<GSSAPILibrary> gssapi( 86 new GSSAPISharedLibrary("/this/library/does/not/exist")); 87 DCHECK(!gssapi.get()->Init()); 88 } 89 90 TEST(HttpAuthGSSAPIPOSIXTest, GSSAPICycle) { 91 scoped_ptr<test::MockGSSAPILibrary> mock_library(new test::MockGSSAPILibrary); 92 DCHECK(mock_library.get()); 93 mock_library->Init(); 94 const char kAuthResponse[] = "Mary had a little lamb"; 95 test::GssContextMockImpl context1( 96 "localhost", // Source name 97 "example.com", // Target name 98 23, // Lifetime 99 *GSS_C_NT_HOSTBASED_SERVICE, // Mechanism 100 0, // Context flags 101 1, // Locally initiated 102 0); // Open 103 test::GssContextMockImpl context2( 104 "localhost", // Source name 105 "example.com", // Target name 106 23, // Lifetime 107 *GSS_C_NT_HOSTBASED_SERVICE, // Mechanism 108 0, // Context flags 109 1, // Locally initiated 110 1); // Open 111 test::MockGSSAPILibrary::SecurityContextQuery queries[] = { 112 test::MockGSSAPILibrary::SecurityContextQuery( 113 "Negotiate", // Package name 114 GSS_S_CONTINUE_NEEDED, // Major response code 115 0, // Minor response code 116 context1, // Context 117 NULL, // Expected input token 118 kAuthResponse), // Output token 119 test::MockGSSAPILibrary::SecurityContextQuery( 120 "Negotiate", // Package name 121 GSS_S_COMPLETE, // Major response code 122 0, // Minor response code 123 context2, // Context 124 kAuthResponse, // Expected input token 125 kAuthResponse) // Output token 126 }; 127 128 for (size_t i = 0; i < arraysize(queries); ++i) { 129 mock_library->ExpectSecurityContext(queries[i].expected_package, 130 queries[i].response_code, 131 queries[i].minor_response_code, 132 queries[i].context_info, 133 queries[i].expected_input_token, 134 queries[i].output_token); 135 } 136 137 OM_uint32 major_status = 0; 138 OM_uint32 minor_status = 0; 139 gss_cred_id_t initiator_cred_handle = NULL; 140 gss_ctx_id_t context_handle = NULL; 141 gss_name_t target_name = NULL; 142 gss_OID mech_type = NULL; 143 OM_uint32 req_flags = 0; 144 OM_uint32 time_req = 25; 145 gss_channel_bindings_t input_chan_bindings = NULL; 146 gss_buffer_desc input_token = { 0, NULL }; 147 gss_OID actual_mech_type= NULL; 148 gss_buffer_desc output_token = { 0, NULL }; 149 OM_uint32 ret_flags = 0; 150 OM_uint32 time_rec = 0; 151 for (size_t i = 0; i < arraysize(queries); ++i) { 152 major_status = mock_library->init_sec_context(&minor_status, 153 initiator_cred_handle, 154 &context_handle, 155 target_name, 156 mech_type, 157 req_flags, 158 time_req, 159 input_chan_bindings, 160 &input_token, 161 &actual_mech_type, 162 &output_token, 163 &ret_flags, 164 &time_rec); 165 CopyBuffer(&input_token, &output_token); 166 ClearBuffer(&output_token); 167 } 168 ClearBuffer(&input_token); 169 major_status = mock_library->delete_sec_context(&minor_status, 170 &context_handle, 171 GSS_C_NO_BUFFER); 172 } 173 174 TEST(HttpAuthGSSAPITest, ParseChallenge_FirstRound) { 175 // The first round should just consist of an unadorned "Negotiate" header. 176 test::MockGSSAPILibrary mock_library; 177 HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate", 178 CHROME_GSS_KRB5_MECH_OID_DESC); 179 std::string challenge_text = "Negotiate"; 180 HttpAuth::ChallengeTokenizer challenge(challenge_text.begin(), 181 challenge_text.end()); 182 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, 183 auth_gssapi.ParseChallenge(&challenge)); 184 } 185 186 TEST(HttpAuthGSSAPITest, ParseChallenge_TwoRounds) { 187 // The first round should just have "Negotiate", and the second round should 188 // have a valid base64 token associated with it. 189 test::MockGSSAPILibrary mock_library; 190 HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate", 191 CHROME_GSS_KRB5_MECH_OID_DESC); 192 std::string first_challenge_text = "Negotiate"; 193 HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(), 194 first_challenge_text.end()); 195 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, 196 auth_gssapi.ParseChallenge(&first_challenge)); 197 198 // Generate an auth token and create another thing. 199 EstablishInitialContext(&mock_library); 200 std::string auth_token; 201 EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, NULL, 202 L"HTTP/intranet.google.com", 203 &auth_token)); 204 205 std::string second_challenge_text = "Negotiate Zm9vYmFy"; 206 HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(), 207 second_challenge_text.end()); 208 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, 209 auth_gssapi.ParseChallenge(&second_challenge)); 210 } 211 212 TEST(HttpAuthGSSAPITest, ParseChallenge_UnexpectedTokenFirstRound) { 213 // If the first round challenge has an additional authentication token, it 214 // should be treated as an invalid challenge from the server. 215 test::MockGSSAPILibrary mock_library; 216 HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate", 217 CHROME_GSS_KRB5_MECH_OID_DESC); 218 std::string challenge_text = "Negotiate Zm9vYmFy"; 219 HttpAuth::ChallengeTokenizer challenge(challenge_text.begin(), 220 challenge_text.end()); 221 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID, 222 auth_gssapi.ParseChallenge(&challenge)); 223 } 224 225 TEST(HttpAuthGSSAPITest, ParseChallenge_MissingTokenSecondRound) { 226 // If a later-round challenge is simply "Negotiate", it should be treated as 227 // an authentication challenge rejection from the server or proxy. 228 test::MockGSSAPILibrary mock_library; 229 HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate", 230 CHROME_GSS_KRB5_MECH_OID_DESC); 231 std::string first_challenge_text = "Negotiate"; 232 HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(), 233 first_challenge_text.end()); 234 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, 235 auth_gssapi.ParseChallenge(&first_challenge)); 236 237 EstablishInitialContext(&mock_library); 238 std::string auth_token; 239 EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, NULL, 240 L"HTTP/intranet.google.com", 241 &auth_token)); 242 std::string second_challenge_text = "Negotiate"; 243 HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(), 244 second_challenge_text.end()); 245 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_REJECT, 246 auth_gssapi.ParseChallenge(&second_challenge)); 247 } 248 249 TEST(HttpAuthGSSAPITest, ParseChallenge_NonBase64EncodedToken) { 250 // If a later-round challenge has an invalid base64 encoded token, it should 251 // be treated as an invalid challenge. 252 test::MockGSSAPILibrary mock_library; 253 HttpAuthGSSAPI auth_gssapi(&mock_library, "Negotiate", 254 CHROME_GSS_KRB5_MECH_OID_DESC); 255 std::string first_challenge_text = "Negotiate"; 256 HttpAuth::ChallengeTokenizer first_challenge(first_challenge_text.begin(), 257 first_challenge_text.end()); 258 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT, 259 auth_gssapi.ParseChallenge(&first_challenge)); 260 261 EstablishInitialContext(&mock_library); 262 std::string auth_token; 263 EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(NULL, NULL, 264 L"HTTP/intranet.google.com", 265 &auth_token)); 266 std::string second_challenge_text = "Negotiate =happyjoy="; 267 HttpAuth::ChallengeTokenizer second_challenge(second_challenge_text.begin(), 268 second_challenge_text.end()); 269 EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_INVALID, 270 auth_gssapi.ParseChallenge(&second_challenge)); 271 } 272 273 } // namespace net 274