peer_proxy_negotiate_auth.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
3  *
4  * Squid software is distributed under GPLv2+ license and includes
5  * contributions from numerous individuals and organizations.
6  * Please see the COPYING and CONTRIBUTORS files for details.
7  */
8 
9 /*
10  * DEBUG: 11 Hypertext Transfer Protocol (HTTP)
11  */
12 
13 #include "squid.h"
14 
15 #if HAVE_AUTH_MODULE_NEGOTIATE && HAVE_KRB5 && HAVE_GSSAPI
16 #include "base64.h"
17 #include "compat/krb5.h"
18 #include "debug/Stream.h"
20 
21 #if HAVE_PROFILE_H
22 #include <profile.h>
23 #endif /* HAVE_PROFILE_H */
24 #if HAVE_ET_COM_ERR_H && !HAVE_KRB5_H
25 #include <et/com_err.h>
26 #endif /* HAVE_COM_ERR_H */
27 #if HAVE_COM_ERR_H
28 #include <com_err.h>
29 #endif /* HAVE_COM_ERR_H */
30 #if HAVE_GSS_H
31 #include <gss.h>
32 #endif
33 #if USE_APPLE_KRB5
34 #define GSSKRB_APPLE_DEPRECATED(x)
35 #endif
36 #if HAVE_GSSAPI_GSSAPI_H
37 #include <gssapi/gssapi.h>
38 #elif HAVE_GSSAPI_H
39 #include <gssapi.h>
40 #endif /* HAVE_GSSAPI_H */
41 #if HAVE_GSSAPI_GSSAPI_EXT_H
42 #include <gssapi/gssapi_ext.h>
43 #endif /* HAVE_GSSAPI_GSSAPI_EXT_H */
44 #if HAVE_GSSAPI_GSSAPI_KRB5_H
45 #include <gssapi/gssapi_krb5.h>
46 #endif /* HAVE_GSSAPI_GSSAPI_KRB5_H */
47 #if HAVE_GSSAPI_GSSAPI_GENERIC_H
48 #include <gssapi/gssapi_generic.h>
49 #endif /* HAVE_GSSAPI_GSSAPI_GENERIC_H */
50 
51 #ifndef gss_nt_service_name
52 #define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE
53 #endif
54 
55 #if !HAVE_ERROR_MESSAGE && HAVE_KRB5_GET_ERROR_MESSAGE
56 #define error_message(code) krb5_get_error_message(kparam.context,code)
57 #elif !HAVE_ERROR_MESSAGE && HAVE_KRB5_GET_ERR_TEXT
58 #define error_message(code) krb5_get_err_text(kparam.context,code)
59 #elif !HAVE_ERROR_MESSAGE
60 static char err_code[17];
61 const char *KRB5_CALLCONV
62 error_message(long code) {
63  snprintf(err_code,16,"%ld",code);
64  return err_code;
65 }
66 #endif
67 
68 #ifndef gss_mech_spnego
69 static gss_OID_desc _gss_mech_spnego =
70 { 6, (void *) "\x2b\x06\x01\x05\x05\x02" };
71 gss_OID gss_mech_spnego = &_gss_mech_spnego;
72 #endif
73 
74 #if USE_IBM_KERBEROS
75 #include <ibm_svc/krb5_svc.h>
76 const char *KRB5_CALLCONV error_message(long code) {
77  char *msg = nullptr;
78  krb5_svc_get_msg(code, &msg);
79  return msg;
80 }
81 #endif
82 
83 /*
84  * Kerberos context and cache structure
85  * Caches authentication details to reduce
86  * number of authentication requests to kdc
87  */
88 static struct kstruct {
89  krb5_context context;
90  krb5_ccache cc;
91 } kparam = {
92  nullptr, nullptr
93 };
94 
95 /*
96  * krb5_create_cache creates a Kerberos file credential cache or a memory
97  * credential cache if supported. The initial key for the principal
98  * principal_name is extracted from the keytab keytab_filename.
99  *
100  * If keytab_filename is NULL the default will be used.
101  * If principal_name is NULL the first working entry of the keytab will be used.
102  */
103 int krb5_create_cache(char *keytab_filename, char *principal_name);
104 
105 /*
106  * krb5_cleanup clears used Keberos memory
107  */
108 void krb5_cleanup(void);
109 
110 /*
111  * check_gss_err checks for gssapi error codes, extracts the error message
112  * and prints it.
113  */
114 int check_gss_err(OM_uint32 major_status, OM_uint32 minor_status,
115  const char *function);
116 
117 int check_gss_err(OM_uint32 major_status, OM_uint32 minor_status,
118  const char *function) {
119  if (GSS_ERROR(major_status)) {
120  OM_uint32 maj_stat, min_stat;
121  OM_uint32 msg_ctx = 0;
122  gss_buffer_desc status_string;
123  char buf[1024];
124  size_t len;
125 
126  len = 0;
127  msg_ctx = 0;
128  while (!msg_ctx) {
129  /* convert major status code (GSS-API error) to text */
130  maj_stat = gss_display_status(&min_stat, major_status,
131  GSS_C_GSS_CODE, GSS_C_NULL_OID, &msg_ctx, &status_string);
132  if (maj_stat == GSS_S_COMPLETE) {
133  if (sizeof(buf) > len + status_string.length + 1) {
134  memcpy(buf + len, status_string.value,
135  status_string.length);
136  len += status_string.length;
137  }
138  gss_release_buffer(&min_stat, &status_string);
139  break;
140  }
141  gss_release_buffer(&min_stat, &status_string);
142  }
143  if (sizeof(buf) > len + 2) {
144  strcpy(buf + len, ". ");
145  len += 2;
146  }
147  msg_ctx = 0;
148  while (!msg_ctx) {
149  /* convert minor status code (underlying routine error) to text */
150  maj_stat = gss_display_status(&min_stat, minor_status,
151  GSS_C_MECH_CODE, GSS_C_NULL_OID, &msg_ctx, &status_string);
152  if (maj_stat == GSS_S_COMPLETE) {
153  if (sizeof(buf) > len + status_string.length) {
154  memcpy(buf + len, status_string.value,
155  status_string.length);
156  len += status_string.length;
157  }
158  gss_release_buffer(&min_stat, &status_string);
159  break;
160  }
161  gss_release_buffer(&min_stat, &status_string);
162  }
163  debugs(11, 5, function << "failed: " << buf);
164  return (1);
165  }
166  return (0);
167 }
168 
169 void krb5_cleanup() {
170  debugs(11, 5, "Cleanup kerberos context");
171  if (kparam.context) {
172  if (kparam.cc)
173  krb5_cc_destroy(kparam.context, kparam.cc);
174  kparam.cc = nullptr;
175  krb5_free_context(kparam.context);
176  kparam.context = nullptr;
177  }
178 }
179 
180 int krb5_create_cache(char *kf, char *pn) {
181 
182 #define KT_PATH_MAX 256
183 #define MAX_RENEW_TIME "365d"
184 #define DEFAULT_SKEW (krb5_deltat) 600
185 
186  static char *keytab_filename = nullptr, *principal_name = nullptr;
187  static krb5_keytab keytab = nullptr;
188  static krb5_keytab_entry entry;
189  static krb5_kt_cursor cursor;
190  static krb5_creds *creds = nullptr;
191 #if HAVE_LIBHEIMDAL_KRB5 && !HAVE_KRB5_GET_RENEWED_CREDS
192  static krb5_creds creds2;
193 #endif
194  static krb5_principal principal = nullptr;
195  static krb5_deltat skew;
196 
197 #if HAVE_KRB5_GET_INIT_CREDS_OPT_ALLOC
198  krb5_get_init_creds_opt *options;
199 #else
200  krb5_get_init_creds_opt options;
201 #endif
202  krb5_error_code code = 0;
203  krb5_deltat rlife;
204 #if HAVE_PROFILE_H && HAVE_KRB5_GET_PROFILE && HAVE_PROFILE_GET_INTEGER && HAVE_PROFILE_RELEASE
205  profile_t profile;
206 #endif
207 #if HAVE_LIBHEIMDAL_KRB5 && !HAVE_KRB5_GET_RENEWED_CREDS
208  krb5_kdc_flags flags;
209 #if HAVE_KRB5_PRINCIPAL_GET_REALM
210  const char *client_realm;
211 #else
212  krb5_realm client_realm;
213 #endif
214 #endif
215  char *mem_cache;
216 
217 restart:
218  /*
219  * Check if credentials need to be renewed
220  */
221  if (creds &&
222  (creds->times.endtime - time(nullptr) > skew) &&
223  (creds->times.renew_till - time(nullptr) > 2 * skew)) {
224  if (creds->times.endtime - time(nullptr) < 2 * skew) {
225 #if HAVE_KRB5_GET_RENEWED_CREDS
226  /* renew ticket */
227  code =
228  krb5_get_renewed_creds(kparam.context, creds, principal,
229  kparam.cc, nullptr);
230 #else
231  /* renew ticket */
232  flags.i = 0;
233  flags.b.renewable = flags.b.renew = 1;
234 
235  code =
236  krb5_cc_get_principal(kparam.context, kparam.cc,
237  &creds2.client);
238  if (code) {
239  debugs(11, 5,
240 
241  "Error while getting principal from credential cache : "
242  << error_message(code));
243  return (1);
244  }
245 #if HAVE_KRB5_PRINCIPAL_GET_REALM
246  client_realm = krb5_principal_get_realm(kparam.context, principal);
247 #else
248  client_realm = krb5_princ_realm(kparam.context, creds2.client);
249 #endif
250  code =
251  krb5_make_principal(kparam.context, &creds2.server,
252  (krb5_const_realm)&client_realm, KRB5_TGS_NAME,
253  (krb5_const_realm)&client_realm, nullptr);
254  if (code) {
255  debugs(11, 5,
256  "Error while getting krbtgt principal : " <<
257  error_message(code));
258  return (1);
259  }
260  code =
261  krb5_get_kdc_cred(kparam.context, kparam.cc, flags, nullptr,
262  nullptr, &creds2, &creds);
263  krb5_free_creds(kparam.context, &creds2);
264 #endif
265  if (code) {
266  if (code == KRB5KRB_AP_ERR_TKT_EXPIRED) {
267  krb5_free_creds(kparam.context, creds);
268  creds = nullptr;
269  /* this can happen because of clock skew */
270  goto restart;
271  }
272  debugs(11, 5,
273  "Error while get credentials : " <<
274  error_message(code));
275  return (1);
276  }
277  }
278  } else {
279  /* reinit */
280  if (!kparam.context) {
281  code = krb5_init_context(&kparam.context);
282  if (code) {
283  debugs(11, 5,
284  "Error while initialising Kerberos library : "
285  << error_message(code));
286  return (1);
287  }
288  }
289 #if HAVE_PROFILE_H && HAVE_KRB5_GET_PROFILE && HAVE_PROFILE_GET_INTEGER && HAVE_PROFILE_RELEASE
290  code = krb5_get_profile(kparam.context, &profile);
291  if (code) {
292  if (profile)
293  profile_release(profile);
294  debugs(11, 5,
295  "Error while getting profile : " <<
296  error_message(code));
297  return (1);
298  }
299  code =
300  profile_get_integer(profile, "libdefaults", "clockskew", nullptr,
301  5 * 60, &skew);
302  if (profile)
303  profile_release(profile);
304  if (code) {
305  debugs(11, 5,
306  "Error while getting clockskew : " <<
307  error_message(code));
308  return (1);
309  }
310 #elif HAVE_LIBHEIMDAL_KRB5
311  skew = krb5_get_max_time_skew(kparam.context);
312 #else
313  skew = DEFAULT_SKEW;
314 #endif
315 
316  if (!kf) {
317  char buf[KT_PATH_MAX], *p;
318 
319  krb5_kt_default_name(kparam.context, buf, KT_PATH_MAX);
320  p = strchr(buf, ':');
321  if (p)
322  ++p;
323  xfree(keytab_filename);
324  keytab_filename = xstrdup(p ? p : buf);
325  } else {
326  keytab_filename = xstrdup(kf);
327  }
328 
329  code = krb5_kt_resolve(kparam.context, keytab_filename, &keytab);
330  if (code) {
331  debugs(11, 5,
332  "Error while resolving keytab filename " <<
333  keytab_filename << " : " << error_message(code));
334  return (1);
335  }
336 
337  if (!pn) {
338  code = krb5_kt_start_seq_get(kparam.context, keytab, &cursor);
339  if (code) {
340  debugs(11, 5,
341  "Error while starting keytab scan : " <<
342  error_message(code));
343  return (1);
344  }
345  code =
346  krb5_kt_next_entry(kparam.context, keytab, &entry, &cursor);
347  krb5_copy_principal(kparam.context, entry.principal,
348  &principal);
349  if (code && code != KRB5_KT_END) {
350  debugs(11, 5,
351  "Error while scanning keytab : " <<
352  error_message(code));
353  return (1);
354  }
355 
356  code = krb5_kt_end_seq_get(kparam.context, keytab, &cursor);
357  if (code) {
358  debugs(11, 5,
359  "Error while ending keytab scan : " <<
360  error_message(code));
361  return (1);
362  }
363 #if HAVE_LIBHEIMDAL_KRB5 || ( HAVE_KRB5_KT_FREE_ENTRY && HAVE_DECL_KRB5_KT_FREE_ENTRY)
364  code = krb5_kt_free_entry(kparam.context, &entry);
365 #else
366  code = krb5_free_keytab_entry_contents(kparam.context, &entry);
367 #endif
368  if (code) {
369  debugs(11, 5,
370  "Error while freeing keytab entry : " <<
371  error_message(code));
372  return (1);
373  }
374 
375  } else {
376  principal_name = xstrdup(pn);
377  }
378 
379  if (!principal) {
380  code =
381  krb5_parse_name(kparam.context, principal_name, &principal);
382  if (code) {
383  debugs(11, 5,
384  "Error while parsing principal name " <<
385  principal_name << " : " << error_message(code));
386  return (1);
387  }
388  }
389 
390  creds = (krb5_creds *) xmalloc(sizeof(*creds));
391  memset(creds, 0, sizeof(*creds));
392 #if HAVE_KRB5_GET_INIT_CREDS_OPT_ALLOC
393  krb5_get_init_creds_opt_alloc(kparam.context, &options);
394 #else
395  krb5_get_init_creds_opt_init(&options);
396 #endif
397  code = krb5_string_to_deltat((char *) MAX_RENEW_TIME, &rlife);
398  if (code != 0 || rlife == 0) {
399  debugs(11, 5,
400  "Error bad lifetime value " << MAX_RENEW_TIME <<
401  " : " << error_message(code));
402  return (1);
403  }
404 #if HAVE_KRB5_GET_INIT_CREDS_OPT_ALLOC
405  krb5_get_init_creds_opt_set_renew_life(options, rlife);
406  code =
407  krb5_get_init_creds_keytab(kparam.context, creds, principal,
408  keytab, 0, nullptr, options);
409 #if HAVE_KRB5_GET_INIT_CREDS_FREE_CONTEXT
410  krb5_get_init_creds_opt_free(kparam.context, options);
411 #else
412  krb5_get_init_creds_opt_free(options);
413 #endif
414 #else
415  krb5_get_init_creds_opt_set_renew_life(&options, rlife);
416  code =
417  krb5_get_init_creds_keytab(kparam.context, creds, principal,
418  keytab, 0, nullptr, &options);
419 #endif
420  if (code) {
421  debugs(11, 5,
422 
423  "Error while initializing credentials from keytab : " <<
424  error_message(code));
425  return (1);
426  }
427 #if !HAVE_KRB5_MEMORY_CACHE
428  mem_cache =
429  (char *) xmalloc(strlen("FILE:/tmp/peer_proxy_negotiate_auth_")
430  + 16);
431  if (!mem_cache) {
432  debugs(11, 5, "Error while allocating memory");
433  return(1);
434  }
435  snprintf(mem_cache,
436  strlen("FILE:/tmp/peer_proxy_negotiate_auth_") + 16,
437  "FILE:/tmp/peer_proxy_negotiate_auth_%d", (int) getpid());
438 #else
439  mem_cache =
440  (char *) xmalloc(strlen("MEMORY:peer_proxy_negotiate_auth_") +
441  16);
442  if (!mem_cache) {
443  debugs(11, 5, "Error while allocating memory");
444  return(1);
445  }
446  snprintf(mem_cache,
447  strlen("MEMORY:peer_proxy_negotiate_auth_") + 16,
448  "MEMORY:peer_proxy_negotiate_auth_%d", (int) getpid());
449 #endif
450 
451  setenv("KRB5CCNAME", mem_cache, 1);
452  code = krb5_cc_resolve(kparam.context, mem_cache, &kparam.cc);
453  xfree(mem_cache);
454  if (code) {
455  debugs(11, 5,
456  "Error while resolving memory credential cache : "
457  << error_message(code));
458  return (1);
459  }
460  code = krb5_cc_initialize(kparam.context, kparam.cc, principal);
461  if (code) {
462  debugs(11, 5,
463 
464  "Error while initializing memory credential cache : " <<
465  error_message(code));
466  return (1);
467  }
468  code = krb5_cc_store_cred(kparam.context, kparam.cc, creds);
469  if (code) {
470  debugs(11, 5,
471  "Error while storing credentials : " <<
472  error_message(code));
473  return (1);
474  }
475 
476  if (!creds->times.starttime)
477  creds->times.starttime = creds->times.authtime;
478  }
479  return (0);
480 }
481 
482 /*
483  * peer_proxy_negotiate_auth gets a GSSAPI token for principal_name
484  * and base64 encodes it.
485  */
486 char *peer_proxy_negotiate_auth(char *principal_name, char *proxy, int flags) {
487  int rc = 0;
488  OM_uint32 major_status, minor_status;
489  gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT;
490  gss_name_t server_name = GSS_C_NO_NAME;
491  gss_buffer_desc service = GSS_C_EMPTY_BUFFER;
492  gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
493  gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
494  char *token = nullptr;
495 
496  setbuf(stdout, nullptr);
497  setbuf(stdin, nullptr);
498 
499  if (!proxy) {
500  debugs(11, 5, "Error : No proxy server name");
501  return nullptr;
502  }
503 
504  if (!(flags & PEER_PROXY_NEGOTIATE_NOKEYTAB)) {
505  if (principal_name)
506  debugs(11, 5,
507  "Creating credential cache for " << principal_name);
508  else
509  debugs(11, 5, "Creating credential cache");
510  rc = krb5_create_cache(nullptr, principal_name);
511  if (rc) {
512  debugs(11, 5, "Error : Failed to create Kerberos cache");
513  krb5_cleanup();
514  return nullptr;
515  }
516  }
517 
518  service.value = (void *) xmalloc(strlen("HTTP") + strlen(proxy) + 2);
519  snprintf((char *) service.value, strlen("HTTP") + strlen(proxy) + 2,
520  "%s@%s", "HTTP", proxy);
521  service.length = strlen((char *) service.value);
522 
523  debugs(11, 5, "Import gss name");
524  major_status = gss_import_name(&minor_status, &service,
525  gss_nt_service_name, &server_name);
526 
527  if (check_gss_err(major_status, minor_status, "gss_import_name()"))
528  goto cleanup;
529 
530  debugs(11, 5, "Initialize gss security context");
531  major_status = gss_init_sec_context(&minor_status,
532  GSS_C_NO_CREDENTIAL,
533  &gss_context,
534  server_name,
535  gss_mech_spnego,
536  0,
537  0,
538  GSS_C_NO_CHANNEL_BINDINGS,
539  &input_token, nullptr, &output_token, nullptr, nullptr);
540 
541  if (check_gss_err(major_status, minor_status, "gss_init_sec_context()"))
542  goto cleanup;
543 
544  debugs(11, 5, "Got token with length " << output_token.length);
545  if (output_token.length) {
546  static char b64buf[8192]; // XXX: 8KB only because base64_encode_bin() used to.
547  struct base64_encode_ctx ctx;
548  base64_encode_init(&ctx);
549  size_t blen = base64_encode_update(&ctx, b64buf, output_token.length, reinterpret_cast<const uint8_t*>(output_token.value));
550  blen += base64_encode_final(&ctx, b64buf+blen);
551  b64buf[blen] = '\0';
552 
553  token = reinterpret_cast<char*>(b64buf);
554  }
555 
556 cleanup:
557  gss_delete_sec_context(&minor_status, &gss_context, nullptr);
558  gss_release_buffer(&minor_status, &service);
559  gss_release_buffer(&minor_status, &input_token);
560  gss_release_buffer(&minor_status, &output_token);
561  gss_release_name(&minor_status, &server_name);
562 
563  return token;
564 }
565 
566 #endif /* HAVE_AUTH_MODULE_NEGOTIATE && HAVE_KRB5 && HAVE_GSSAPI */
567 
#define xmalloc
#define gss_nt_service_name
int check_gss_err(OM_uint32 major_status, OM_uint32 minor_status, const char *function, int log, int sout)
size_t base64_encode_final(struct base64_encode_ctx *ctx, char *dst)
Definition: base64.c:308
#define PEER_PROXY_NEGOTIATE_NOKEYTAB
#define xstrdup
void base64_encode_init(struct base64_encode_ctx *ctx)
Definition: base64.c:232
#define xfree
int code
Definition: smb-errors.c:145
size_t base64_encode_update(struct base64_encode_ctx *ctx, char *dst, size_t length, const uint8_t *src)
Definition: base64.c:265
#define debugs(SECTION, LEVEL, CONTENT)
Definition: Stream.h:192

 

Introduction

Documentation

Support

Miscellaneous