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
15namespace net {
16
17namespace {
18
19// gss_buffer_t helpers.
20void 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
28void 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
41void 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
50const char kInitialAuthResponse[] = "Mary had a little lamb";
51
52void 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
75TEST(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)
85TEST(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
92TEST(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
178TEST(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
190TEST(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
215TEST(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
228TEST(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
251TEST(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