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

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors