1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
14  */
15 
16 /*
17  * SPNEGO back-end for Kerberos.  See [MS-KILE]
18  */
19 
20 #include <sys/types.h>
21 #include <gssapi/gssapi_ext.h>
22 #include <gssapi/gssapi_krb5.h>
23 #include <krb5.h>
24 #include "smbd.h"
25 #include "smbd_authsvc.h"
26 
27 /* From krb5/krb/pac.c (should have been exported) */
28 #define	PAC_LOGON_INFO		1
29 
30 typedef struct krb5ssp_backend {
31 	gss_ctx_id_t		be_gssctx;
32 	char			*be_username;
33 	gss_buffer_desc		be_authz_pac;
34 	krb5_context		be_kctx;
35 	krb5_pac		be_kpac;
36 	krb5_data		be_pac;
37 } krb5ssp_backend_t;
38 
39 static uint32_t
40 get_authz_data_pac(
41 	gss_ctx_id_t context_handle,
42 	gss_buffer_t ad_data);
43 
44 static uint32_t
45 get_ssnkey(authsvc_context_t *ctx);
46 
47 
48 /*
49  * Initialize this context for Kerberos, if possible.
50  *
51  * Should not get here unless libsmb smb_config_get_negtok
52  * includes the Kerberos5 Mech OIDs in our spnego hint.
53  *
54  * Todo: allocate ctx->ctx_backend
55  * See: krb5_gss_accept_sec_context()
56  */
57 int
58 smbd_krb5ssp_init(authsvc_context_t *ctx)
59 {
60 	krb5ssp_backend_t *be;
61 
62 	be = malloc(sizeof (*be));
63 	if (be == 0)
64 		return (NT_STATUS_NO_MEMORY);
65 	bzero(be, sizeof (*be));
66 	be->be_gssctx = GSS_C_NO_CONTEXT;
67 	ctx->ctx_backend = be;
68 
69 	return (0);
70 }
71 
72 /*
73  * Todo: free ctx->ctx_backend
74  */
75 void
76 smbd_krb5ssp_fini(authsvc_context_t *ctx)
77 {
78 	krb5ssp_backend_t *be = ctx->ctx_backend;
79 	uint32_t minor;
80 
81 	if (be == NULL)
82 		return;
83 
84 	if (be->be_kctx != NULL) {
85 		krb5_free_data_contents(be->be_kctx, &be->be_pac);
86 
87 		if (be->be_kpac != NULL)
88 			krb5_pac_free(be->be_kctx, be->be_kpac);
89 
90 		krb5_free_context(be->be_kctx);
91 	}
92 
93 	(void) gss_release_buffer(NULL, &be->be_authz_pac);
94 
95 	free(be->be_username);
96 
97 	if (be->be_gssctx != GSS_C_NO_CONTEXT) {
98 		(void) gss_delete_sec_context(&minor, &be->be_gssctx,
99 		    GSS_C_NO_BUFFER);
100 	}
101 
102 	free(be);
103 }
104 
105 /*
106  * Handle a Kerberos auth message.
107  *
108  * State across messages is in ctx->ctx_backend
109  */
110 int
111 smbd_krb5ssp_work(authsvc_context_t *ctx)
112 {
113 	gss_buffer_desc	intok, outtok;
114 	gss_buffer_desc namebuf;
115 	krb5ssp_backend_t *be = ctx->ctx_backend;
116 	gss_name_t gname = NULL;
117 	OM_uint32 major, minor, ret_flags;
118 	gss_OID name_type = GSS_C_NULL_OID;
119 	gss_OID mech_type = GSS_C_NULL_OID;
120 	krb5_error_code kerr;
121 	uint32_t status;
122 
123 	intok.length = ctx->ctx_ibodylen;
124 	intok.value  = ctx->ctx_ibodybuf;
125 	bzero(&outtok, sizeof (gss_buffer_desc));
126 	bzero(&namebuf, sizeof (gss_buffer_desc));
127 
128 	/* Do this early, for error message support. */
129 	kerr = krb5_init_context(&be->be_kctx);
130 	if (kerr != 0) {
131 		smbd_report("krb5ssp, krb5_init_ctx: %s",
132 		    krb5_get_error_message(be->be_kctx, kerr));
133 		return (NT_STATUS_INTERNAL_ERROR);
134 	}
135 
136 	major = gss_accept_sec_context(&minor, &be->be_gssctx,
137 	    GSS_C_NO_CREDENTIAL, &intok,
138 	    GSS_C_NO_CHANNEL_BINDINGS, &gname, &mech_type, &outtok,
139 	    &ret_flags, NULL, NULL);
140 
141 	if (outtok.length == 0)
142 		ctx->ctx_obodylen = 0;
143 	else if (outtok.length <= ctx->ctx_obodylen) {
144 		ctx->ctx_obodylen = outtok.length;
145 		(void) memcpy(ctx->ctx_obodybuf, outtok.value, outtok.length);
146 		free(outtok.value);
147 		outtok.value = NULL;
148 	} else {
149 		free(ctx->ctx_obodybuf);
150 		ctx->ctx_obodybuf = outtok.value;
151 		ctx->ctx_obodylen = outtok.length;
152 		outtok.value = NULL;
153 	}
154 
155 	if (GSS_ERROR(major)) {
156 		smbd_report("krb5ssp: gss_accept_sec_context, "
157 		    "mech=0x%x, major=0x%x, minor=0x%x",
158 		    (int)mech_type, major, minor);
159 		smbd_report(" krb5: %s",
160 		    krb5_get_error_message(be->be_kctx, minor));
161 		return (NT_STATUS_WRONG_PASSWORD);
162 	}
163 
164 	switch (major) {
165 	case GSS_S_COMPLETE:
166 		break;
167 	case GSS_S_CONTINUE_NEEDED:
168 		if (outtok.length > 0) {
169 			ctx->ctx_orawtype = LSA_MTYPE_ES_CONT;
170 			/* becomes NT_STATUS_MORE_PROCESSING_REQUIRED */
171 			return (0);
172 		}
173 		return (NT_STATUS_WRONG_PASSWORD);
174 	default:
175 		return (NT_STATUS_WRONG_PASSWORD);
176 	}
177 
178 	/*
179 	 * OK, we got GSS_S_COMPLETE.  Get the name so we can use it
180 	 * in log messages if we get failures decoding the PAC etc.
181 	 * Then get the PAC, decode it, build the logon token.
182 	 */
183 
184 	if (gname != NULL && GSS_S_COMPLETE ==
185 	    gss_display_name(&minor, gname, &namebuf, &name_type)) {
186 		/* Save the user name. */
187 		be->be_username = strdup(namebuf.value);
188 		(void) gss_release_buffer(&minor, &namebuf);
189 		(void) gss_release_name(&minor, &gname);
190 		if (be->be_username == NULL) {
191 			return (NT_STATUS_NO_MEMORY);
192 		}
193 	}
194 
195 	/*
196 	 * Extract the KRB5_AUTHDATA_WIN2K_PAC data.
197 	 */
198 	status = get_authz_data_pac(be->be_gssctx,
199 	    &be->be_authz_pac);
200 	if (status)
201 		return (status);
202 
203 	kerr = krb5_pac_parse(be->be_kctx, be->be_authz_pac.value,
204 	    be->be_authz_pac.length, &be->be_kpac);
205 	if (kerr) {
206 		smbd_report("krb5ssp, krb5_pac_parse: %s",
207 		    krb5_get_error_message(be->be_kctx, kerr));
208 		return (NT_STATUS_UNSUCCESSFUL);
209 	}
210 
211 	kerr = krb5_pac_get_buffer(be->be_kctx, be->be_kpac,
212 	    PAC_LOGON_INFO, &be->be_pac);
213 	if (kerr) {
214 		smbd_report("krb5ssp, krb5_pac_get_buffer: %s",
215 		    krb5_get_error_message(be->be_kctx, kerr));
216 		return (NT_STATUS_UNSUCCESSFUL);
217 	}
218 
219 	ctx->ctx_token = calloc(1, sizeof (smb_token_t));
220 	if (ctx->ctx_token == NULL)
221 		return (NT_STATUS_NO_MEMORY);
222 
223 	status = smb_decode_krb5_pac(ctx->ctx_token, be->be_pac.data,
224 	    be->be_pac.length);
225 	if (status)
226 		return (status);
227 
228 	status = get_ssnkey(ctx);
229 	if (status)
230 		return (status);
231 
232 	if (!smb_token_setup_common(ctx->ctx_token))
233 		return (NT_STATUS_UNSUCCESSFUL);
234 
235 	/* Success! */
236 	ctx->ctx_orawtype = LSA_MTYPE_ES_DONE;
237 
238 	return (0);
239 }
240 
241 /*
242  * See: GSS_KRB5_EXTRACT_AUTHZ_DATA_FROM_SEC_CONTEXT_OID
243  * and: KRB5_AUTHDATA_WIN2K_PAC
244  */
245 static const gss_OID_desc
246 oid_ex_authz_data_pac = {
247 	13, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x0a\x81\x00" };
248 
249 /*
250  * See: krb5_gss_inquire_sec_context_by_oid()
251  * and krb5_gss_inquire_sec_context_by_oid_ops[],
252  * gss_krb5int_extract_authz_data_from_sec_context()
253  */
254 static uint32_t
255 get_authz_data_pac(
256 	gss_ctx_id_t context_handle,
257 	gss_buffer_t ad_data)
258 {
259 	gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET;
260 	OM_uint32 major, minor;
261 	uint32_t status = NT_STATUS_UNSUCCESSFUL;
262 
263 	if (ad_data == NULL)
264 		goto out;
265 
266 	major = gss_inquire_sec_context_by_oid(
267 	    &minor,
268 	    context_handle,
269 	    (gss_OID)&oid_ex_authz_data_pac,
270 	    &data_set);
271 	if (GSS_ERROR(major)) {
272 		smbd_report("krb5ssp, gss_inquire...PAC, "
273 		    "major=0x%x, minor=0x%x", major, minor);
274 		goto out;
275 	}
276 
277 	if ((data_set == GSS_C_NO_BUFFER_SET) || (data_set->count == 0)) {
278 		goto out;
279 	}
280 
281 	/* Only need the first element? */
282 	ad_data->length = data_set->elements[0].length;
283 	ad_data->value = malloc(ad_data->length);
284 	if (ad_data->value == NULL) {
285 		status = NT_STATUS_NO_MEMORY;
286 		goto out;
287 	}
288 	bcopy(data_set->elements[0].value, ad_data->value, ad_data->length);
289 	status = 0;
290 
291 out:
292 	(void) gss_release_buffer_set(&minor, &data_set);
293 
294 	return (status);
295 }
296 
297 /*
298  * Get the session key, and save it in the token.
299  *
300  * See: krb5_gss_inquire_sec_context_by_oid(),
301  * krb5_gss_inquire_sec_context_by_oid_ops[], and
302  * gss_krb5int_inq_session_key
303  */
304 static uint32_t
305 get_ssnkey(authsvc_context_t *ctx)
306 {
307 	krb5ssp_backend_t *be = ctx->ctx_backend;
308 	gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET;
309 	OM_uint32 major, minor;
310 	size_t keylen;
311 	uint32_t status = NT_STATUS_UNSUCCESSFUL;
312 
313 	major = gss_inquire_sec_context_by_oid(&minor,
314 	    be->be_gssctx, GSS_C_INQ_SSPI_SESSION_KEY, &data_set);
315 	if (GSS_ERROR(major)) {
316 		smbd_report("krb5ssp, failed to get session key, "
317 		    "major=0x%x, minor=0x%x", major, minor);
318 		goto out;
319 	}
320 
321 	/*
322 	 * The key is in the first element
323 	 */
324 	if (data_set == GSS_C_NO_BUFFER_SET ||
325 	    data_set->count == 0 ||
326 	    data_set->elements[0].length == 0 ||
327 	    data_set->elements[0].value == NULL) {
328 		smbd_report("krb5ssp: Session key is missing");
329 		goto out;
330 	}
331 	if ((keylen = data_set->elements[0].length) < SMBAUTH_HASH_SZ) {
332 		smbd_report("krb5ssp: Session key too short (%d)",
333 		    data_set->elements[0].length);
334 		goto out;
335 	}
336 
337 	ctx->ctx_token->tkn_ssnkey.val = malloc(keylen);
338 	if (ctx->ctx_token->tkn_ssnkey.val == NULL) {
339 		status = NT_STATUS_NO_MEMORY;
340 		goto out;
341 	}
342 	ctx->ctx_token->tkn_ssnkey.len = keylen;
343 	bcopy(data_set->elements[0].value,
344 	    ctx->ctx_token->tkn_ssnkey.val, keylen);
345 	status = 0;
346 
347 out:
348 	(void) gss_release_buffer_set(&minor, &data_set);
349 	return (status);
350 }
351