2 * Copyright (c) 2019 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * This is a plugin by which bx509d can validate Negotiate tokens.
38 * negotiate_token_validator = {
43 #define _DEFAULT_SOURCE
47 #include <sys/types.h>
61 #include <common_plugin.h>
62 #include <gssapi/gssapi.h>
63 #include <token_validator_plugin.h>
66 display_status(krb5_context context
,
73 gss_buffer_desc buf
= GSS_C_EMPTY_BUFFER
;
81 gss_release_buffer(&dmin
, &buf
);
82 dmaj
= gss_display_status(&dmin
, major
, GSS_C_GSS_CODE
, GSS_C_NO_OID
,
84 if (GSS_ERROR(dmaj
) ||
85 buf
.length
>= INT_MAX
||
86 asprintf(&s
, "%s%s%.*s", gmsg
? gmsg
: "", gmsg
? ": " : "",
87 (int)buf
.length
, (char *)buf
.value
) == -1 ||
95 } while (!GSS_ERROR(dmaj
) && more
);
96 if (mech_type
!= GSS_C_NO_OID
) {
98 gss_release_buffer(&dmin
, &buf
);
99 dmaj
= gss_display_status(&dmin
, major
, GSS_C_MECH_CODE
, mech_type
,
101 if (GSS_ERROR(dmaj
) ||
102 asprintf(&s
, "%s%s%.*s", gmmsg
? gmmsg
: "", gmmsg
? ": " : "",
103 (int)buf
.length
, (char *)buf
.value
) == -1 ||
111 } while (!GSS_ERROR(dmaj
) && more
);
114 krb5_set_error_message(context
, ENOMEM
, "Error displaying GSS-API "
117 krb5_set_error_message(context
, EACCES
, "%s%s%s%s", gmmsg
,
118 gmmsg
? " (" : "", gmmsg
? gmmsg
: "",
121 krb5_prepend_error_message(context
, EACCES
, "Failed to validate "
122 "Negotiate token due to error examining "
123 "GSS-API security context");
125 krb5_prepend_error_message(context
, EACCES
, "Failed to validate "
126 "Negotiate token due to error accepting "
127 "GSS-API security context token");
129 krb5_prepend_error_message(context
, EACCES
, "Failed to validate "
130 "Negotiate token due to error acquiring "
131 "GSS-API default acceptor credential");
135 static KRB5_LIB_CALL krb5_error_code
137 krb5_context context
,
139 const char *token_type
,
141 const char * const *audiences
,
143 krb5_boolean
*result
,
144 krb5_principal
*actual_principal
,
145 krb5_times
*token_times
)
147 gss_buffer_desc adisplay_name
= GSS_C_EMPTY_BUFFER
;
148 gss_buffer_desc idisplay_name
= GSS_C_EMPTY_BUFFER
;
149 gss_buffer_desc output_token
= GSS_C_EMPTY_BUFFER
;
150 gss_buffer_desc input_token
;
151 gss_cred_id_t acred
= GSS_C_NO_CREDENTIAL
;
152 gss_ctx_id_t gctx
= GSS_C_NO_CONTEXT
;
153 gss_name_t aname
= GSS_C_NO_NAME
;
154 gss_name_t iname
= GSS_C_NO_NAME
;
155 gss_OID mech_type
= GSS_C_NO_OID
;
156 const char *kt
= krb5_config_get_string(context
, NULL
, "kdc",
157 "negotiate_token_validator",
159 OM_uint32 major
, minor
, ret_flags
, time_rec
;
161 char *token_decoded
= NULL
;
162 void *token_copy
= NULL
;
163 char *princ_str
= NULL
;
166 if (strcmp(token_type
, "Negotiate") != 0)
167 return KRB5_PLUGIN_NO_HANDLE
;
170 gss_key_value_element_desc store_keytab_kv
;
171 gss_key_value_set_desc store
;
172 gss_OID_desc mech_set
[2] = { *GSS_KRB5_MECHANISM
, *GSS_SPNEGO_MECHANISM
};
173 gss_OID_set_desc mechs
= { 2, mech_set
};
175 store_keytab_kv
.key
= "keytab";
176 store_keytab_kv
.value
= kt
;
177 store
.elements
= &store_keytab_kv
;
179 major
= gss_acquire_cred_from(&minor
, GSS_C_NO_NAME
, GSS_C_INDEFINITE
,
180 &mechs
, GSS_C_ACCEPT
, &store
, &acred
, NULL
,
182 if (major
!= GSS_S_COMPLETE
)
183 return display_status(context
, major
, minor
, acred
, gctx
, mech_type
);
186 major
= gss_set_neg_mechs(&minor
, acred
, &mechs
);
187 if (major
!= GSS_S_COMPLETE
)
188 return display_status(context
, major
, minor
, acred
, gctx
, mech_type
);
189 } /* else we'll use the default credential */
191 if ((token_decoded
= malloc(token
->length
)) == NULL
||
192 (token_copy
= calloc(1, token
->length
+ 1)) == NULL
)
195 memcpy(token_copy
, token
->data
, token
->length
);
196 if ((ret
= rk_base64_decode(token_copy
, token_decoded
)) <= 0) {
197 krb5_set_error_message(context
, EACCES
, "Negotiate token malformed");
202 input_token
.value
= token_decoded
;
203 input_token
.length
= ret
;
204 major
= gss_accept_sec_context(&minor
, &gctx
, acred
, &input_token
, NULL
,
205 &iname
, &mech_type
, &output_token
,
206 &ret_flags
, &time_rec
, NULL
);
208 if (mech_type
== GSS_C_NO_OID
||
209 !gss_oid_equal(mech_type
, GSS_KRB5_MECHANISM
)) {
210 krb5_set_error_message(context
, ret
= EACCES
, "Negotiate token used "
211 "non-Kerberos mechanism");
215 if (major
!= GSS_S_COMPLETE
) {
216 ret
= display_status(context
, major
, minor
, acred
, gctx
, mech_type
);
222 major
= gss_inquire_context(&minor
, gctx
, NULL
, &aname
, NULL
, NULL
,
224 if (major
== GSS_S_COMPLETE
)
225 major
= gss_display_name(&minor
, aname
, &adisplay_name
, NULL
);
226 if (major
== GSS_S_COMPLETE
)
227 major
= gss_display_name(&minor
, iname
, &idisplay_name
, NULL
);
228 if (major
!= GSS_S_COMPLETE
) {
229 ret
= display_status(context
, major
, minor
, acred
, gctx
, mech_type
);
235 for (i
= 0; i
< naudiences
; i
++) {
236 const char *s
= adisplay_name
.value
;
237 size_t slen
= adisplay_name
.length
;
238 size_t len
= strlen(audiences
[i
]);
240 if (slen
>= sizeof("HTTP/") - 1 &&
241 slen
>= sizeof("HTTP/") - 1 + len
&&
242 memcmp(s
, "HTTP/", sizeof("HTTP/") - 1) == 0 &&
243 memcmp(s
+ sizeof("HTTP/") - 1, audiences
[i
], len
) == 0 &&
244 s
[sizeof("HTTP/") - 1 + len
] == '@')
247 if (i
== naudiences
) {
248 /* This handles the case where naudiences == 0 as an error */
249 krb5_set_error_message(context
, EACCES
, "Negotiate token used "
250 "wrong HTTP service host acceptor name");
254 if ((princ_str
= calloc(1, idisplay_name
.length
+ 1)) == NULL
)
256 memcpy(princ_str
, idisplay_name
.value
, idisplay_name
.length
);
257 if ((ret
= krb5_parse_name(context
, princ_str
, actual_principal
)))
260 /* XXX Need name attributes to get authtime/starttime/renew_till */
261 token_times
->authtime
= 0;
262 token_times
->starttime
= time(NULL
) - 300;
263 token_times
->endtime
= token_times
->starttime
+ 300 + time_rec
;
264 token_times
->renew_till
= 0;
270 ret
= krb5_enomem(context
);
272 gss_delete_sec_context(&minor
, &gctx
, NULL
);
273 gss_release_buffer(&minor
, &adisplay_name
);
274 gss_release_buffer(&minor
, &idisplay_name
);
275 gss_release_buffer(&minor
, &output_token
);
276 gss_release_cred(&minor
, &acred
);
277 gss_release_name(&minor
, &aname
);
278 gss_release_name(&minor
, &iname
);
285 static KRB5_LIB_CALL krb5_error_code
286 negotiate_init(krb5_context context
, void **c
)
292 static KRB5_LIB_CALL
void
293 negotiate_fini(void *c
)
297 static krb5plugin_token_validator_ftable plug_desc
=
298 { 1, negotiate_init
, negotiate_fini
, validate
};
300 static krb5plugin_token_validator_ftable
*plugs
[] = { &plug_desc
};
303 negotiate_get_instance(const char *libname
)
305 if (strcmp(libname
, "krb5") == 0)
306 return krb5_get_instance(libname
);
311 krb5_plugin_load_ft kdc_token_validator_plugin_load
;
313 krb5_error_code KRB5_CALLCONV
314 kdc_token_validator_plugin_load(heim_pcontext context
,
315 krb5_get_instance_func_t
*get_instance
,
317 krb5_plugin_common_ftable_cp
**plugins
)
319 *get_instance
= negotiate_get_instance
;
320 *num_plugins
= sizeof(plugs
) / sizeof(plugs
[0]);
321 *plugins
= (krb5_plugin_common_ftable_cp
*)plugs
;