1/*
2 * Copyright (c) 2004, PADL Software Pty Ltd.
3 * All rights reserved.
4 *
5 * Portions Copyright (c) 2009 Apple Inc. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 *
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * 3. Neither the name of PADL Software nor the names of its contributors
19 *    may be used to endorse or promote products derived from this software
20 *    without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY PADL SOFTWARE AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED.  IN NO EVENT SHALL PADL SOFTWARE OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 */
34
35#include "spnego_locl.h"
36
37/*
38 * Apparently Microsoft got the OID wrong, and used
39 * 1.2.840.48018.1.2.2 instead. We need both this and
40 * the correct Kerberos OID here in order to deal with
41 * this. Because this is manifest in SPNEGO only I'd
42 * prefer to deal with this here rather than inside the
43 * Kerberos mechanism.
44 */
45gss_OID_desc _gss_spnego_mskrb_mechanism_oid_desc =
46    {9, rk_UNCONST("\x2a\x86\x48\x82\xf7\x12\x01\x02\x02")};
47
48gss_OID_desc _gss_spnego_krb5_mechanism_oid_desc =
49    {9, rk_UNCONST("\x2a\x86\x48\x86\xf7\x12\x01\x02\x02")};
50
51/*
52 * Allocate a SPNEGO context handle
53 */
54OM_uint32 GSSAPI_CALLCONV
55_gss_spnego_alloc_sec_context (OM_uint32 * minor_status,
56			       gss_ctx_id_t *context_handle)
57{
58    gssspnego_ctx ctx;
59
60    ctx = calloc(1, sizeof(*ctx));
61    if (ctx == NULL) {
62	*minor_status = ENOMEM;
63	return GSS_S_FAILURE;
64    }
65
66    ctx->NegTokenInit_mech_types.value = NULL;
67    ctx->NegTokenInit_mech_types.length = 0;
68
69    ctx->preferred_mech_type = GSS_C_NO_OID;
70    ctx->selected_mech_type = GSS_C_NO_OID;
71    ctx->negotiated_mech_type = GSS_C_NO_OID;
72
73    ctx->negotiated_ctx_id = GSS_C_NO_CONTEXT;
74
75    ctx->mech_src_name = GSS_C_NO_NAME;
76
77    ctx->flags.open = 0;
78    ctx->flags.local = 0;
79    ctx->flags.peer_require_mic = 0;
80    ctx->flags.require_mic = 0;
81    ctx->flags.verified_mic = 0;
82
83    HEIMDAL_MUTEX_init(&ctx->ctx_id_mutex);
84
85    *context_handle = (gss_ctx_id_t)ctx;
86
87    return GSS_S_COMPLETE;
88}
89
90/*
91 * Free a SPNEGO context handle. The caller must have acquired
92 * the lock before this is called.
93 */
94OM_uint32 GSSAPI_CALLCONV _gss_spnego_internal_delete_sec_context
95           (OM_uint32 *minor_status,
96            gss_ctx_id_t *context_handle,
97            gss_buffer_t output_token
98           )
99{
100    gssspnego_ctx ctx;
101    OM_uint32 ret, minor;
102
103    *minor_status = 0;
104
105    if (context_handle == NULL) {
106	return GSS_S_NO_CONTEXT;
107    }
108
109    if (output_token != GSS_C_NO_BUFFER) {
110	output_token->length = 0;
111	output_token->value = NULL;
112    }
113
114    ctx = (gssspnego_ctx)*context_handle;
115    *context_handle = GSS_C_NO_CONTEXT;
116
117    if (ctx == NULL) {
118	return GSS_S_NO_CONTEXT;
119    }
120
121    if (ctx->NegTokenInit_mech_types.value)
122	free(ctx->NegTokenInit_mech_types.value);
123
124    ctx->negotiated_mech_type = GSS_C_NO_OID;
125    ctx->selected_mech_type = GSS_C_NO_OID;
126
127    gss_release_name(&minor, &ctx->target_name);
128    gss_release_name(&minor, &ctx->mech_src_name);
129
130    if (ctx->negotiated_ctx_id != GSS_C_NO_CONTEXT) {
131	ret = gss_delete_sec_context(minor_status,
132				     &ctx->negotiated_ctx_id,
133				     NULL);
134	ctx->negotiated_ctx_id = GSS_C_NO_CONTEXT;
135    } else {
136	ret = GSS_S_COMPLETE;
137    }
138
139    HEIMDAL_MUTEX_unlock(&ctx->ctx_id_mutex);
140    HEIMDAL_MUTEX_destroy(&ctx->ctx_id_mutex);
141
142    free(ctx);
143
144    return ret;
145}
146
147void
148_gss_spnego_fixup_ntlm(gssspnego_ctx ctx)
149{
150    if (gss_oid_equal(ctx->negotiated_mech_type, GSS_NTLM_MECHANISM)) {
151	gss_buffer_set_t buffer_set = GSS_C_NO_BUFFER_SET;
152	OM_uint32 junk;
153	gss_inquire_sec_context_by_oid(&junk, ctx->negotiated_ctx_id,
154				       GSS_C_NTLM_RESET_KEYS,
155				       &buffer_set);
156	gss_release_buffer_set(&junk, &buffer_set);
157    }
158}
159
160/*
161 * For compatability with the Windows SPNEGO implementation, the
162 * default is to ignore the mechListMIC unless CFX is used and
163 * a non-preferred mechanism was negotiated
164 */
165
166int
167_gss_spnego_require_mechlist_mic(gssspnego_ctx ctx)
168{
169    gss_buffer_set_t buffer_set = GSS_C_NO_BUFFER_SET;
170    int require_mic;
171    OM_uint32 minor;
172
173    require_mic = 1;
174
175    /* Acceptor requested it: mandatory to honour */
176    if (ctx->flags.peer_require_mic)
177	return 1;
178
179    /* Protocol can't handle mechListMIC (HTTP Negotiate) */
180    if (ctx->flags.protocol_require_no_mic)
181	return 0;
182
183    /*
184     * Check whether peer indicated implicit support for updated SPNEGO
185     * (eg. in the Kerberos case by using CFX)
186     */
187    if (gss_inquire_sec_context_by_oid(&minor, ctx->negotiated_ctx_id,
188				       GSS_C_PEER_HAS_UPDATED_SPNEGO,
189				       &buffer_set) == GSS_S_COMPLETE) {
190	require_mic = 1;
191	gss_release_buffer_set(&minor, &buffer_set);
192    }
193
194    /*
195     * Don't require mic for NTLM because
196     *  - Windows servers to have negTokenResp.negResult set for the acceptor to send the mic.
197     *  - SnowLeopard smb server can't handle it
198     * So if we are the initiator and using NTLM, don't send the acceptor status.
199     */
200    if (ctx->flags.local && gss_oid_equal(ctx->negotiated_mech_type, GSS_NTLM_MECHANISM))
201	require_mic = 0;
202
203    /* Safe-to-omit MIC rules follow */
204
205    if (gss_oid_equal(ctx->negotiated_mech_type, ctx->preferred_mech_type)) {
206	ctx->flags.safe_omit = 1;
207    } else if (gss_oid_equal(ctx->negotiated_mech_type, &_gss_spnego_krb5_mechanism_oid_desc) &&
208	       gss_oid_equal(ctx->preferred_mech_type, &_gss_spnego_mskrb_mechanism_oid_desc)) {
209	ctx->flags.safe_omit = 1;
210    }
211
212    return require_mic;
213}
214
215static int
216add_mech_type(gss_OID mech_type,
217	      int includeMSCompatOID,
218	      MechTypeList *mechtypelist)
219{
220    MechType mech;
221    int ret;
222
223    if (includeMSCompatOID &&
224	gss_oid_equal(mech_type, &_gss_spnego_krb5_mechanism_oid_desc)) {
225	ret = der_get_oid(_gss_spnego_mskrb_mechanism_oid_desc.elements,
226			  _gss_spnego_mskrb_mechanism_oid_desc.length,
227			  &mech,
228			  NULL);
229	if (ret)
230	    return ret;
231	ret = add_MechTypeList(mechtypelist, &mech);
232	free_MechType(&mech);
233	if (ret)
234	    return ret;
235    }
236    ret = der_get_oid(mech_type->elements, mech_type->length, &mech, NULL);
237    if (ret)
238	return ret;
239    ret = add_MechTypeList(mechtypelist, &mech);
240    free_MechType(&mech);
241    return ret;
242}
243
244
245OM_uint32 GSSAPI_CALLCONV
246_gss_spnego_indicate_mechtypelist(OM_uint32 *minor_status,
247				  gss_name_t target_name,
248				  OM_uint32 (*func)(void *, gss_name_t, const gss_cred_id_t, gss_OID),
249				  void *userctx,
250				  int includeMSCompatOID,
251				  const gss_cred_id_t cred_handle,
252				  MechTypeList *mechtypelist,
253				  gss_OID *preferred_mech)
254{
255    gss_OID_set supported_mechs = GSS_C_NO_OID_SET;
256    gss_OID first_mech = GSS_C_NO_OID;
257    OM_uint32 ret, junk;
258    int present = 0;
259    size_t i;
260
261    mechtypelist->len = 0;
262    mechtypelist->val = NULL;
263
264    if (cred_handle) {
265	ret = gss_inquire_cred(minor_status,
266			       cred_handle,
267			       NULL,
268			       NULL,
269			       NULL,
270			       &supported_mechs);
271    } else {
272	ret = gss_indicate_mechs(minor_status, &supported_mechs);
273    }
274
275    if (ret != GSS_S_COMPLETE) {
276	return ret;
277    }
278
279    if (supported_mechs->count == 0) {
280	*minor_status = ENOENT;
281	gss_release_oid_set(minor_status, &supported_mechs);
282	return GSS_S_FAILURE;
283    }
284
285    /*
286     * Propose Kerberos mech first if we have Kerberos credentials/supported mechs
287     */
288
289    ret = gss_test_oid_set_member(&junk, GSS_KRB5_MECHANISM, supported_mechs, &present);
290    if (ret == GSS_S_COMPLETE && present) {
291	ret = (*func)(userctx, target_name, cred_handle, GSS_KRB5_MECHANISM);
292	if (ret == GSS_S_COMPLETE) {
293	    ret = add_mech_type(GSS_KRB5_MECHANISM,
294				includeMSCompatOID,
295				mechtypelist);
296	    if (!GSS_ERROR(ret)) {
297		if (includeMSCompatOID)
298		    first_mech = &_gss_spnego_mskrb_mechanism_oid_desc;
299		else
300		    first_mech = GSS_KRB5_MECHANISM;
301	    }
302#ifdef __APPLE_PRIVATE__
303	    (void)add_mech_type(GSS_APPL_LKDC_SUPPORTED, 0, mechtypelist);
304#endif
305	}
306    }
307    ret = GSS_S_COMPLETE;
308
309    /*
310     * Now lets, check all other mechs
311     */
312
313    for (i = 0; i < supported_mechs->count; i++) {
314	OM_uint32 subret;
315	if (gss_oid_equal(&supported_mechs->elements[i], GSS_SPNEGO_MECHANISM))
316	    continue;
317	if (gss_oid_equal(&supported_mechs->elements[i], GSS_KRB5_MECHANISM))
318	    continue;
319	if (gss_oid_equal(&supported_mechs->elements[i], GSS_NETLOGON_MECHANISM))
320	    continue;
321
322	subret = (*func)(userctx, target_name, cred_handle, &supported_mechs->elements[i]);
323	if (subret != GSS_S_COMPLETE)
324	    continue;
325
326	ret = add_mech_type(&supported_mechs->elements[i],
327			    includeMSCompatOID,
328			    mechtypelist);
329	if (ret != 0) {
330	    *minor_status = ret;
331	    ret = GSS_S_FAILURE;
332	    break;
333	}
334	if (first_mech == GSS_C_NO_OID)
335	    first_mech = &supported_mechs->elements[i];
336    }
337
338    if (mechtypelist->len == 0) {
339	gss_release_oid_set(minor_status, &supported_mechs);
340	*minor_status = 0;
341	return GSS_S_BAD_MECH;
342    }
343
344    if (preferred_mech != NULL) {
345	*preferred_mech = _gss_mg_support_mechanism(first_mech);
346    }
347    gss_release_oid_set(minor_status, &supported_mechs);
348
349    return ret;
350}
351
352/*
353 *
354 */
355
356OM_uint32
357_gss_spnego_verify_mechtypes_mic(OM_uint32 *minor_status,
358				 gssspnego_ctx ctx,
359				 heim_octet_string *mic)
360{
361    gss_buffer_desc mic_buf;
362    OM_uint32 major_status;
363
364    if (ctx->flags.verified_mic) {
365	/* This doesn't make sense, we've already verified it? */
366	*minor_status = 0;
367	return GSS_S_DUPLICATE_TOKEN;
368    }
369
370    mic_buf.length = mic->length;
371    mic_buf.value  = mic->data;
372
373    major_status = gss_verify_mic(minor_status,
374				  ctx->negotiated_ctx_id,
375				  &ctx->NegTokenInit_mech_types,
376				  &mic_buf,
377				  NULL);
378    if (major_status == GSS_S_UNAVAILABLE) {
379	_gss_mg_log(10, "mech doesn't support MIC, allowing anyway");
380    } else if (major_status) {
381	return gss_mg_set_error_string(GSS_SPNEGO_MECHANISM,
382				       GSS_S_DEFECTIVE_TOKEN, *minor_status,
383				       "SPNEGO peer sent invalid mechListMIC");
384    }
385    ctx->flags.verified_mic = 1;
386
387    *minor_status = 0;
388
389    return GSS_S_COMPLETE;
390}
391