1/* $OpenBSD: gss-genr.c,v 1.20 2009/06/22 05:39:28 dtucker Exp $ */
2
3/*
4 * Copyright (c) 2001-2009 Simon Wilkinson. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "includes.h"
28
29#ifdef GSSAPI
30
31#include <sys/types.h>
32#include <sys/param.h>
33
34#include <stdarg.h>
35#include <string.h>
36#include <unistd.h>
37
38#include "xmalloc.h"
39#include "buffer.h"
40#include "log.h"
41#include "ssh2.h"
42#include "cipher.h"
43#include "key.h"
44#include "kex.h"
45#ifdef __APPLE_CRYPTO__
46#include "ossl-evp.h"
47#else
48#include <openssl/evp.h>
49#endif
50
51#include "ssh-gss.h"
52
53extern u_char *session_id2;
54extern u_int session_id2_len;
55
56typedef struct {
57	char *encoded;
58	gss_OID oid;
59} ssh_gss_kex_mapping;
60
61/*
62 * XXX - It would be nice to find a more elegant way of handling the
63 * XXX   passing of the key exchange context to the userauth routines
64 */
65
66Gssctxt *gss_kex_context = NULL;
67
68static ssh_gss_kex_mapping *gss_enc2oid = NULL;
69
70int
71ssh_gssapi_oid_table_ok() {
72	return (gss_enc2oid != NULL);
73}
74
75/*
76 * Return a list of the gss-group1-sha1 mechanisms supported by this program
77 *
78 * We test mechanisms to ensure that we can use them, to avoid starting
79 * a key exchange with a bad mechanism
80 */
81
82char *
83ssh_gssapi_client_mechanisms(const char *host, const char *client) {
84	gss_OID_set gss_supported;
85	OM_uint32 min_status;
86
87	if (GSS_ERROR(gss_indicate_mechs(&min_status, &gss_supported)))
88		return NULL;
89
90	return(ssh_gssapi_kex_mechs(gss_supported, ssh_gssapi_check_mechanism,
91	    host, client));
92}
93
94char *
95ssh_gssapi_kex_mechs(gss_OID_set gss_supported, ssh_gssapi_check_fn *check,
96    const char *host, const char *client) {
97	Buffer buf;
98	size_t i;
99	int oidpos, enclen;
100	char *mechs, *encoded;
101	u_char digest[EVP_MAX_MD_SIZE];
102	char deroid[2];
103	const EVP_MD *evp_md = EVP_md5();
104	EVP_MD_CTX md;
105
106	if (gss_enc2oid != NULL) {
107		for (i = 0; gss_enc2oid[i].encoded != NULL; i++)
108			xfree(gss_enc2oid[i].encoded);
109		xfree(gss_enc2oid);
110	}
111
112	gss_enc2oid = xmalloc(sizeof(ssh_gss_kex_mapping) *
113	    (gss_supported->count + 1));
114
115	buffer_init(&buf);
116
117	oidpos = 0;
118	for (i = 0; i < gss_supported->count; i++) {
119		if (gss_supported->elements[i].length < 128 &&
120		    (*check)(NULL, &(gss_supported->elements[i]), host, client)) {
121
122			deroid[0] = SSH_GSS_OIDTYPE;
123			deroid[1] = gss_supported->elements[i].length;
124
125			EVP_DigestInit(&md, evp_md);
126			EVP_DigestUpdate(&md, deroid, 2);
127			EVP_DigestUpdate(&md,
128			    gss_supported->elements[i].elements,
129			    gss_supported->elements[i].length);
130			EVP_DigestFinal(&md, digest, NULL);
131
132			encoded = xmalloc(EVP_MD_size(evp_md) * 2);
133			enclen = __b64_ntop(digest, EVP_MD_size(evp_md),
134			    encoded, EVP_MD_size(evp_md) * 2);
135
136			if (oidpos != 0)
137				buffer_put_char(&buf, ',');
138
139			buffer_append(&buf, KEX_GSS_GEX_SHA1_ID,
140			    sizeof(KEX_GSS_GEX_SHA1_ID) - 1);
141			buffer_append(&buf, encoded, enclen);
142			buffer_put_char(&buf, ',');
143			buffer_append(&buf, KEX_GSS_GRP1_SHA1_ID,
144			    sizeof(KEX_GSS_GRP1_SHA1_ID) - 1);
145			buffer_append(&buf, encoded, enclen);
146			buffer_put_char(&buf, ',');
147			buffer_append(&buf, KEX_GSS_GRP14_SHA1_ID,
148			    sizeof(KEX_GSS_GRP14_SHA1_ID) - 1);
149			buffer_append(&buf, encoded, enclen);
150
151			gss_enc2oid[oidpos].oid = &(gss_supported->elements[i]);
152			gss_enc2oid[oidpos].encoded = encoded;
153			oidpos++;
154		}
155	}
156	gss_enc2oid[oidpos].oid = NULL;
157	gss_enc2oid[oidpos].encoded = NULL;
158
159	buffer_put_char(&buf, '\0');
160
161	mechs = xmalloc(buffer_len(&buf));
162	buffer_get(&buf, mechs, buffer_len(&buf));
163	buffer_free(&buf);
164
165	if (strlen(mechs) == 0) {
166		xfree(mechs);
167		mechs = NULL;
168	}
169
170	return (mechs);
171}
172
173gss_OID
174ssh_gssapi_id_kex(Gssctxt *ctx, char *name, int kex_type) {
175	int i = 0;
176
177	switch (kex_type) {
178	case KEX_GSS_GRP1_SHA1:
179		if (strlen(name) < sizeof(KEX_GSS_GRP1_SHA1_ID))
180			return GSS_C_NO_OID;
181		name += sizeof(KEX_GSS_GRP1_SHA1_ID) - 1;
182		break;
183	case KEX_GSS_GRP14_SHA1:
184		if (strlen(name) < sizeof(KEX_GSS_GRP14_SHA1_ID))
185			return GSS_C_NO_OID;
186		name += sizeof(KEX_GSS_GRP14_SHA1_ID) - 1;
187		break;
188	case KEX_GSS_GEX_SHA1:
189		if (strlen(name) < sizeof(KEX_GSS_GEX_SHA1_ID))
190			return GSS_C_NO_OID;
191		name += sizeof(KEX_GSS_GEX_SHA1_ID) - 1;
192		break;
193	default:
194		return GSS_C_NO_OID;
195	}
196
197	while (gss_enc2oid[i].encoded != NULL &&
198	    strcmp(name, gss_enc2oid[i].encoded) != 0)
199		i++;
200
201	if (gss_enc2oid[i].oid != NULL && ctx != NULL)
202		ssh_gssapi_set_oid(ctx, gss_enc2oid[i].oid);
203
204	return gss_enc2oid[i].oid;
205}
206
207/* Check that the OID in a data stream matches that in the context */
208int
209ssh_gssapi_check_oid(Gssctxt *ctx, void *data, size_t len)
210{
211	return (ctx != NULL && ctx->oid != GSS_C_NO_OID &&
212	    ctx->oid->length == len &&
213	    memcmp(ctx->oid->elements, data, len) == 0);
214}
215
216/* Set the contexts OID from a data stream */
217void
218ssh_gssapi_set_oid_data(Gssctxt *ctx, void *data, size_t len)
219{
220	if (ctx->oid != GSS_C_NO_OID) {
221		xfree(ctx->oid->elements);
222		xfree(ctx->oid);
223	}
224	ctx->oid = xmalloc(sizeof(gss_OID_desc));
225	ctx->oid->length = len;
226	ctx->oid->elements = xmalloc(len);
227	memcpy(ctx->oid->elements, data, len);
228}
229
230/* Set the contexts OID */
231void
232ssh_gssapi_set_oid(Gssctxt *ctx, gss_OID oid)
233{
234	ssh_gssapi_set_oid_data(ctx, oid->elements, oid->length);
235}
236
237/* All this effort to report an error ... */
238void
239ssh_gssapi_error(Gssctxt *ctxt)
240{
241	char *s;
242
243	s = ssh_gssapi_last_error(ctxt, NULL, NULL);
244	debug("%s", s);
245	xfree(s);
246}
247
248char *
249ssh_gssapi_last_error(Gssctxt *ctxt, OM_uint32 *major_status,
250    OM_uint32 *minor_status)
251{
252	OM_uint32 lmin;
253	gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
254	OM_uint32 ctx;
255	Buffer b;
256	char *ret;
257
258	buffer_init(&b);
259
260	if (major_status != NULL)
261		*major_status = ctxt->major;
262	if (minor_status != NULL)
263		*minor_status = ctxt->minor;
264
265	ctx = 0;
266	/* The GSSAPI error */
267	do {
268		gss_display_status(&lmin, ctxt->major,
269		    GSS_C_GSS_CODE, ctxt->oid, &ctx, &msg);
270
271		buffer_append(&b, msg.value, msg.length);
272		buffer_put_char(&b, '\n');
273
274		gss_release_buffer(&lmin, &msg);
275	} while (ctx != 0);
276
277	/* The mechanism specific error */
278	do {
279		gss_display_status(&lmin, ctxt->minor,
280		    GSS_C_MECH_CODE, ctxt->oid, &ctx, &msg);
281
282		buffer_append(&b, msg.value, msg.length);
283		buffer_put_char(&b, '\n');
284
285		gss_release_buffer(&lmin, &msg);
286	} while (ctx != 0);
287
288	buffer_put_char(&b, '\0');
289	ret = xmalloc(buffer_len(&b));
290	buffer_get(&b, ret, buffer_len(&b));
291	buffer_free(&b);
292	return (ret);
293}
294
295/*
296 * Initialise our GSSAPI context. We use this opaque structure to contain all
297 * of the data which both the client and server need to persist across
298 * {accept,init}_sec_context calls, so that when we do it from the userauth
299 * stuff life is a little easier
300 */
301void
302ssh_gssapi_build_ctx(Gssctxt **ctx)
303{
304	*ctx = xcalloc(1, sizeof (Gssctxt));
305	(*ctx)->context = GSS_C_NO_CONTEXT;
306	(*ctx)->name = GSS_C_NO_NAME;
307	(*ctx)->oid = GSS_C_NO_OID;
308	(*ctx)->creds = GSS_C_NO_CREDENTIAL;
309	(*ctx)->client = GSS_C_NO_NAME;
310	(*ctx)->client_creds = GSS_C_NO_CREDENTIAL;
311}
312
313/* Delete our context, providing it has been built correctly */
314void
315ssh_gssapi_delete_ctx(Gssctxt **ctx)
316{
317	OM_uint32 ms;
318
319	if ((*ctx) == NULL)
320		return;
321	if ((*ctx)->context != GSS_C_NO_CONTEXT)
322		gss_delete_sec_context(&ms, &(*ctx)->context, GSS_C_NO_BUFFER);
323	if ((*ctx)->name != GSS_C_NO_NAME)
324		gss_release_name(&ms, &(*ctx)->name);
325	if ((*ctx)->oid != GSS_C_NO_OID) {
326		xfree((*ctx)->oid->elements);
327		xfree((*ctx)->oid);
328		(*ctx)->oid = GSS_C_NO_OID;
329	}
330	if ((*ctx)->creds != GSS_C_NO_CREDENTIAL)
331		gss_release_cred(&ms, &(*ctx)->creds);
332	if ((*ctx)->client != GSS_C_NO_NAME)
333		gss_release_name(&ms, &(*ctx)->client);
334	if ((*ctx)->client_creds != GSS_C_NO_CREDENTIAL)
335		gss_release_cred(&ms, &(*ctx)->client_creds);
336
337	xfree(*ctx);
338	*ctx = NULL;
339}
340
341/*
342 * Wrapper to init_sec_context
343 * Requires that the context contains:
344 *	oid
345 *	server name (from ssh_gssapi_import_name)
346 */
347OM_uint32
348ssh_gssapi_init_ctx(Gssctxt *ctx, int deleg_creds, gss_buffer_desc *recv_tok,
349    gss_buffer_desc* send_tok, OM_uint32 *flags)
350{
351	int deleg_flag = 0;
352
353	if (deleg_creds) {
354		deleg_flag = GSS_C_DELEG_FLAG;
355		debug("Delegating credentials");
356	}
357
358	ctx->major = gss_init_sec_context(&ctx->minor,
359	    ctx->client_creds, &ctx->context, ctx->name, ctx->oid,
360	    GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG | deleg_flag,
361	    0, NULL, recv_tok, NULL, send_tok, flags, NULL);
362
363	if (GSS_ERROR(ctx->major))
364		ssh_gssapi_error(ctx);
365
366	return (ctx->major);
367}
368
369/* Create a service name for the given host */
370OM_uint32
371ssh_gssapi_import_name(Gssctxt *ctx, const char *host)
372{
373	gss_buffer_desc gssbuf;
374	char *val;
375
376	xasprintf(&val, "host@%s", host);
377	gssbuf.value = val;
378	gssbuf.length = strlen(gssbuf.value);
379
380	if ((ctx->major = gss_import_name(&ctx->minor,
381	    &gssbuf, GSS_C_NT_HOSTBASED_SERVICE, &ctx->name)))
382		ssh_gssapi_error(ctx);
383
384	xfree(gssbuf.value);
385	return (ctx->major);
386}
387
388OM_uint32
389ssh_gssapi_client_identity(Gssctxt *ctx, const char *name)
390{
391	gss_buffer_desc gssbuf;
392	gss_name_t gssname;
393	OM_uint32 status;
394	gss_OID_set oidset;
395
396	gssbuf.value = (void *) name;
397	gssbuf.length = strlen(gssbuf.value);
398
399	gss_create_empty_oid_set(&status, &oidset);
400	gss_add_oid_set_member(&status, ctx->oid, &oidset);
401
402	ctx->major = gss_import_name(&ctx->minor, &gssbuf,
403	    GSS_C_NT_USER_NAME, &gssname);
404
405	if (!ctx->major)
406		ctx->major = gss_acquire_cred(&ctx->minor,
407		    gssname, 0, oidset, GSS_C_INITIATE,
408		    &ctx->client_creds, NULL, NULL);
409
410	gss_release_name(&status, &gssname);
411	gss_release_oid_set(&status, &oidset);
412
413	if (ctx->major)
414		ssh_gssapi_error(ctx);
415
416	return(ctx->major);
417}
418
419OM_uint32
420ssh_gssapi_sign(Gssctxt *ctx, gss_buffer_t buffer, gss_buffer_t hash)
421{
422	if (ctx == NULL)
423		return -1;
424
425	if ((ctx->major = gss_get_mic(&ctx->minor, ctx->context,
426	    GSS_C_QOP_DEFAULT, buffer, hash)))
427		ssh_gssapi_error(ctx);
428
429	return (ctx->major);
430}
431
432/* Priviledged when used by server */
433OM_uint32
434ssh_gssapi_checkmic(Gssctxt *ctx, gss_buffer_t gssbuf, gss_buffer_t gssmic)
435{
436	if (ctx == NULL)
437		return -1;
438
439	ctx->major = gss_verify_mic(&ctx->minor, ctx->context,
440	    gssbuf, gssmic, NULL);
441
442	return (ctx->major);
443}
444
445void
446ssh_gssapi_buildmic(Buffer *b, const char *user, const char *service,
447    const char *context)
448{
449	buffer_init(b);
450	buffer_put_string(b, session_id2, session_id2_len);
451	buffer_put_char(b, SSH2_MSG_USERAUTH_REQUEST);
452	buffer_put_cstring(b, user);
453	buffer_put_cstring(b, service);
454	buffer_put_cstring(b, context);
455}
456
457int
458ssh_gssapi_check_mechanism(Gssctxt **ctx, gss_OID oid, const char *host,
459    const char *client)
460{
461	gss_buffer_desc token = GSS_C_EMPTY_BUFFER;
462	OM_uint32 major, minor;
463	gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"};
464	Gssctxt *intctx = NULL;
465
466	if (ctx == NULL)
467		ctx = &intctx;
468
469	/* RFC 4462 says we MUST NOT do SPNEGO */
470	if (oid->length == spnego_oid.length &&
471	    (memcmp(oid->elements, spnego_oid.elements, oid->length) == 0))
472		return 0; /* false */
473
474	ssh_gssapi_build_ctx(ctx);
475	ssh_gssapi_set_oid(*ctx, oid);
476	major = ssh_gssapi_import_name(*ctx, host);
477
478	if (!GSS_ERROR(major) && client)
479		major = ssh_gssapi_client_identity(*ctx, client);
480
481	if (!GSS_ERROR(major)) {
482		major = ssh_gssapi_init_ctx(*ctx, 0, GSS_C_NO_BUFFER, &token,
483		    NULL);
484		gss_release_buffer(&minor, &token);
485		if ((*ctx)->context != GSS_C_NO_CONTEXT)
486			gss_delete_sec_context(&minor, &(*ctx)->context,
487			    GSS_C_NO_BUFFER);
488	}
489
490	if (GSS_ERROR(major) || intctx != NULL)
491		ssh_gssapi_delete_ctx(ctx);
492
493	return (!GSS_ERROR(major));
494}
495
496int
497ssh_gssapi_credentials_updated(Gssctxt *ctxt) {
498	static gss_name_t saved_name = GSS_C_NO_NAME;
499	static OM_uint32 saved_lifetime = 0;
500	static gss_OID saved_mech = GSS_C_NO_OID;
501	static gss_name_t name;
502	static OM_uint32 last_call = 0;
503	OM_uint32 lifetime, now, major, minor;
504	int equal;
505	gss_cred_usage_t usage = GSS_C_INITIATE;
506
507	now = time(NULL);
508
509	if (ctxt) {
510		debug("Rekey has happened - updating saved versions");
511
512		if (saved_name != GSS_C_NO_NAME)
513			gss_release_name(&minor, &saved_name);
514
515		major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL,
516		    &saved_name, &saved_lifetime, NULL, NULL);
517
518		if (!GSS_ERROR(major)) {
519			saved_mech = ctxt->oid;
520		        saved_lifetime+= now;
521		} else {
522			/* Handle the error */
523		}
524		return 0;
525	}
526
527	if (now - last_call < 10)
528		return 0;
529
530	last_call = now;
531
532	if (saved_mech == GSS_C_NO_OID)
533		return 0;
534
535	major = gss_inquire_cred(&minor, GSS_C_NO_CREDENTIAL,
536	    &name, &lifetime, NULL, NULL);
537	if (major == GSS_S_CREDENTIALS_EXPIRED)
538		return 0;
539	else if (GSS_ERROR(major))
540		return 0;
541
542	major = gss_compare_name(&minor, saved_name, name, &equal);
543	gss_release_name(&minor, &name);
544	if (GSS_ERROR(major))
545		return 0;
546
547	if (equal && (saved_lifetime < lifetime + now - 10))
548		return 1;
549
550	return 0;
551}
552
553#endif /* GSSAPI */
554