xref: /illumos-gate/usr/src/lib/libsmbfs/smb/krb5ssp.c (revision 8fd04b8338ed5093ec2d1e668fa620b7de44c177)
1 /*
2  * Copyright (c) 2000, Boris Popov
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *    This product includes software developed by Boris Popov.
16  * 4. Neither the name of the author nor the names of any co-contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32 
33 /*
34  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
35  * Use is subject to license terms.
36  */
37 
38 /*
39  * Kerberos V Security Support Provider
40  *
41  * Based on code previously in ctx.c (from Boris Popov?)
42  * but then mostly rewritten at Sun.
43  */
44 
45 #include <errno.h>
46 #include <stdio.h>
47 #include <stddef.h>
48 #include <stdlib.h>
49 #include <unistd.h>
50 #include <strings.h>
51 #include <netdb.h>
52 #include <libintl.h>
53 #include <xti.h>
54 #include <assert.h>
55 
56 #include <sys/types.h>
57 #include <sys/time.h>
58 #include <sys/byteorder.h>
59 #include <sys/socket.h>
60 #include <sys/fcntl.h>
61 
62 #include <netinet/in.h>
63 #include <netinet/tcp.h>
64 #include <arpa/inet.h>
65 
66 #include <netsmb/smb.h>
67 #include <netsmb/smb_lib.h>
68 #include <netsmb/mchain.h>
69 
70 #include "private.h"
71 #include "charsets.h"
72 #include "spnego.h"
73 #include "derparse.h"
74 #include "ssp.h"
75 
76 #include <kerberosv5/krb5.h>
77 #include <kerberosv5/com_err.h>
78 
79 /* RFC 1964 token ID codes */
80 #define	KRB_AP_REQ	1
81 #define	KRB_AP_REP	2
82 #define	KRB_ERROR	3
83 
84 extern MECH_OID g_stcMechOIDList [];
85 
86 typedef struct krb5ssp_state {
87 	/* Filled in by krb5ssp_init_client */
88 	krb5_context ss_krb5ctx;	/* krb5 context (ptr) */
89 	krb5_ccache ss_krb5cc; 		/* credentials cache (ptr) */
90 	krb5_principal ss_krb5clp;	/* client principal (ptr) */
91 	/* Filled in by krb5ssp_get_tkt */
92 	krb5_auth_context ss_auth;	/* auth ctx. w/ server (ptr) */
93 } krb5ssp_state_t;
94 
95 
96 /*
97  * adds a GSSAPI wrapper
98  */
99 int
100 krb5ssp_tkt2gtok(uchar_t *tkt, ulong_t tktlen,
101     uchar_t **gtokp, ulong_t *gtoklenp)
102 {
103 	ulong_t		len;
104 	ulong_t		bloblen = tktlen;
105 	uchar_t		krbapreq[2] = {	KRB_AP_REQ, 0 };
106 	uchar_t 	*blob = NULL;		/* result */
107 	uchar_t 	*b;
108 
109 	bloblen += sizeof (krbapreq);
110 	bloblen += g_stcMechOIDList[spnego_mech_oid_Kerberos_V5].iLen;
111 	len = bloblen;
112 	bloblen = ASNDerCalcTokenLength(bloblen, bloblen);
113 	if ((blob = malloc(bloblen)) == NULL) {
114 		DPRINT("malloc");
115 		return (ENOMEM);
116 	}
117 
118 	b = blob;
119 	b += ASNDerWriteToken(b, SPNEGO_NEGINIT_APP_CONSTRUCT, NULL, len);
120 	b += ASNDerWriteOID(b, spnego_mech_oid_Kerberos_V5);
121 	memcpy(b, krbapreq, sizeof (krbapreq));
122 	b += sizeof (krbapreq);
123 
124 	assert(b + tktlen == blob + bloblen);
125 	memcpy(b, tkt, tktlen);
126 	*gtoklenp = bloblen;
127 	*gtokp = blob;
128 	return (0);
129 }
130 
131 /*
132  * See "Windows 2000 Kerberos Interoperability" paper by
133  * Christopher Nebergall.  RC4 HMAC is the W2K default but
134  * Samba support lagged (not due to Samba itself, but due to OS'
135  * Kerberos implementations.)
136  *
137  * Only session enc type should matter, not ticket enc type,
138  * per Sam Hartman on krbdev.
139  *
140  * Preauthentication failure topics in krb-protocol may help here...
141  * try "John Brezak" and/or "Clifford Neuman" too.
142  */
143 static krb5_enctype kenctypes[] = {
144 	ENCTYPE_ARCFOUR_HMAC,	/* defined in krb5.h */
145 	ENCTYPE_DES_CBC_MD5,
146 	ENCTYPE_DES_CBC_CRC,
147 	ENCTYPE_NULL
148 };
149 
150 static const int rq_opts =
151     AP_OPTS_USE_SUBKEY | AP_OPTS_MUTUAL_REQUIRED;
152 
153 /*
154  * Obtain a kerberos ticket for the host we're connecting to.
155  * (This does the KRB_TGS exchange.)
156  */
157 static int
158 krb5ssp_get_tkt(krb5ssp_state_t *ss, char *server,
159 	uchar_t **tktp, ulong_t *tktlenp)
160 {
161 	krb5_context	kctx = ss->ss_krb5ctx;
162 	krb5_ccache	kcc  = ss->ss_krb5cc;
163 	krb5_data	indata = {0};
164 	krb5_data	outdata = {0};
165 	krb5_error_code	kerr = 0;
166 	const char	*fn = NULL;
167 	uchar_t 	*tkt;
168 
169 	/* Should have these from krb5ssp_init_client. */
170 	if (kctx == NULL || kcc == NULL) {
171 		fn = "null kctx or kcc";
172 		kerr = EINVAL;
173 		goto out;
174 	}
175 
176 	kerr = krb5_set_default_tgs_enctypes(kctx, kenctypes);
177 	if (kerr != 0) {
178 		fn = "krb5_set_default_tgs_enctypes";
179 		goto out;
180 	}
181 
182 	/* Override the krb5 library default. */
183 	indata.data = "";
184 
185 	kerr = krb5_mk_req(kctx, &ss->ss_auth, rq_opts, "cifs", server,
186 	    &indata, kcc, &outdata);
187 	if (kerr != 0) {
188 		fn = "krb5_mk_req";
189 		goto out;
190 	}
191 	if ((tkt = malloc(outdata.length)) == NULL) {
192 		kerr = ENOMEM;
193 		fn = "malloc signing key";
194 		goto out;
195 	}
196 	memcpy(tkt, outdata.data, outdata.length);
197 	*tktp = tkt;
198 	*tktlenp = outdata.length;
199 	kerr = 0;
200 
201 out:
202 	if (kerr) {
203 		if (fn == NULL)
204 			fn = "?";
205 		DPRINT("%s err 0x%x: %s", fn, kerr, error_message(kerr));
206 		if (kerr <= 0 || kerr > ESTALE)
207 			kerr = EAUTH;
208 	}
209 
210 	if (outdata.data)
211 		krb5_free_data_contents(kctx, &outdata);
212 
213 	/* Free kctx in krb5ssp_destroy */
214 	return (kerr);
215 }
216 
217 
218 /*
219  * Build an RFC 1964 KRB_AP_REQ message
220  * The caller puts on the SPNEGO wrapper.
221  */
222 int
223 krb5ssp_put_request(struct ssp_ctx *sp, struct mbdata *out_mb)
224 {
225 	int err;
226 	struct smb_ctx *ctx = sp->smb_ctx;
227 	krb5ssp_state_t *ss = sp->sp_private;
228 	uchar_t 	*tkt = NULL;
229 	ulong_t		tktlen;
230 	uchar_t 	*gtok = NULL;		/* gssapi token */
231 	ulong_t		gtoklen;		/* gssapi token length */
232 	char		*prin = ctx->ct_srvname;
233 
234 	if ((err = krb5ssp_get_tkt(ss, prin, &tkt, &tktlen)) != 0)
235 		goto out;
236 	if ((err = krb5ssp_tkt2gtok(tkt, tktlen, &gtok, &gtoklen)) != 0)
237 		goto out;
238 
239 	if ((err = mb_init_sz(out_mb, gtoklen)) != 0)
240 		goto out;
241 	if ((err = mb_put_mem(out_mb, gtok, gtoklen, MB_MSYSTEM)) != 0)
242 		goto out;
243 
244 	if (ctx->ct_vcflags & SMBV_WILL_SIGN)
245 		ctx->ct_hflags2 |= SMB_FLAGS2_SECURITY_SIGNATURE;
246 
247 out:
248 	if (gtok)
249 		free(gtok);
250 	if (tkt)
251 		free(tkt);
252 
253 	return (err);
254 }
255 
256 /*
257  * Unwrap a GSS-API encapsulated RFC 1964 reply message,
258  * i.e. type KRB_AP_REP or KRB_ERROR.
259  */
260 int
261 krb5ssp_get_reply(struct ssp_ctx *sp, struct mbdata *in_mb)
262 {
263 	krb5ssp_state_t *ss = sp->sp_private;
264 	mbuf_t *m = in_mb->mb_top;
265 	int err = EBADRPC;
266 	int dlen, rc;
267 	long actual_len, token_len;
268 	uchar_t *data;
269 	krb5_data ap = {0};
270 	krb5_ap_rep_enc_part *reply = NULL;
271 
272 	/* cheating: this mbuf is contiguous */
273 	assert(m->m_data == in_mb->mb_pos);
274 	data = (uchar_t *)m->m_data;
275 	dlen = m->m_len;
276 
277 	/*
278 	 * Peel off the GSS-API wrapper.  Looks like:
279 	 *   AppToken: 60 81 83
280 	 *  OID(KRB5): 06 09 2a 86 48 86 f7 12 01 02 02
281 	 * KRB_AP_REP: 02 00
282 	 */
283 	rc = ASNDerCheckToken(data, SPNEGO_NEGINIT_APP_CONSTRUCT,
284 	    0, dlen, &token_len, &actual_len);
285 	if (rc != SPNEGO_E_SUCCESS) {
286 		DPRINT("no AppToken? rc=0x%x", rc);
287 		goto out;
288 	}
289 	if (dlen < actual_len)
290 		goto out;
291 	data += actual_len;
292 	dlen -= actual_len;
293 
294 	/* OID (KRB5) */
295 	rc = ASNDerCheckOID(data, spnego_mech_oid_Kerberos_V5,
296 	    dlen, &actual_len);
297 	if (rc != SPNEGO_E_SUCCESS) {
298 		DPRINT("no OID? rc=0x%x", rc);
299 		goto out;
300 	}
301 	if (dlen < actual_len)
302 		goto out;
303 	data += actual_len;
304 	dlen -= actual_len;
305 
306 	/* KRB_AP_REP or KRB_ERROR */
307 	if (data[0] != KRB_AP_REP) {
308 		DPRINT("KRB5 type: %d", data[1]);
309 		goto out;
310 	}
311 	if (dlen < 2)
312 		goto out;
313 	data += 2;
314 	dlen -= 2;
315 
316 	/*
317 	 * Now what's left should be a krb5 reply
318 	 * NB: ap is NOT allocated, so don't free it.
319 	 */
320 	ap.length = dlen;
321 	ap.data = (char *)data;
322 	rc = krb5_rd_rep(ss->ss_krb5ctx, ss->ss_auth, &ap, &reply);
323 	if (rc != 0) {
324 		DPRINT("krb5_rd_rep: err 0x%x (%s)",
325 		    rc, error_message(rc));
326 		err = EAUTH;
327 		goto out;
328 	}
329 
330 	/*
331 	 * Have the decoded reply.  Save anything?
332 	 *
333 	 * NB: If this returns an error, we will get
334 	 * no more calls into this back-end module.
335 	 */
336 	err = 0;
337 
338 out:
339 	if (reply != NULL)
340 		krb5_free_ap_rep_enc_part(ss->ss_krb5ctx, reply);
341 	if (err)
342 		DPRINT("ret %d", err);
343 
344 	return (err);
345 }
346 
347 /*
348  * krb5ssp_final
349  *
350  * Called after successful authentication.
351  * Setup the MAC key for signing.
352  */
353 int
354 krb5ssp_final(struct ssp_ctx *sp)
355 {
356 	struct smb_ctx *ctx = sp->smb_ctx;
357 	krb5ssp_state_t *ss = sp->sp_private;
358 	krb5_keyblock	*ssn_key = NULL;
359 	int err, len;
360 
361 	/*
362 	 * Save the session key, used for SMB signing
363 	 * and possibly other consumers (RPC).
364 	 */
365 	err = krb5_auth_con_getlocalsubkey(
366 	    ss->ss_krb5ctx, ss->ss_auth, &ssn_key);
367 	if (err != 0) {
368 		DPRINT("_getlocalsubkey, err=0x%x (%s)",
369 		    err, error_message(err));
370 		if (err <= 0 || err > ESTALE)
371 			err = EAUTH;
372 		goto out;
373 	}
374 	memset(ctx->ct_ssn_key, 0, SMBIOC_HASH_SZ);
375 	if ((len = ssn_key->length) > SMBIOC_HASH_SZ)
376 		len = SMBIOC_HASH_SZ;
377 	memcpy(ctx->ct_ssn_key, ssn_key->contents, len);
378 
379 	/*
380 	 * Set the MAC key on the first successful auth.
381 	 */
382 	if ((ctx->ct_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) &&
383 	    (ctx->ct_mackey == NULL)) {
384 		ctx->ct_mackeylen = ssn_key->length;
385 		ctx->ct_mackey = malloc(ctx->ct_mackeylen);
386 		if (ctx->ct_mackey == NULL) {
387 			ctx->ct_mackeylen = 0;
388 			err = ENOMEM;
389 			goto out;
390 		}
391 		memcpy(ctx->ct_mackey, ssn_key->contents,
392 		    ctx->ct_mackeylen);
393 		/*
394 		 * Apparently, the server used seq. no. zero
395 		 * for our previous message, so next is two.
396 		 */
397 		ctx->ct_mac_seqno = 2;
398 	}
399 	err = 0;
400 
401 out:
402 	if (ssn_key)
403 		krb5_free_keyblock(ss->ss_krb5ctx, ssn_key);
404 
405 	return (err);
406 }
407 
408 /*
409  * krb5ssp_next_token
410  *
411  * See ssp.c: ssp_ctx_next_token
412  */
413 int
414 krb5ssp_next_token(struct ssp_ctx *sp, struct mbdata *in_mb,
415 	struct mbdata *out_mb)
416 {
417 	int err;
418 
419 	/*
420 	 * Note: in_mb == NULL on the first call.
421 	 */
422 	if (in_mb) {
423 		err = krb5ssp_get_reply(sp, in_mb);
424 		if (err)
425 			goto out;
426 	}
427 
428 	if (out_mb) {
429 		err = krb5ssp_put_request(sp, out_mb);
430 	} else
431 		err = krb5ssp_final(sp);
432 
433 out:
434 	if (err)
435 		DPRINT("ret: %d", err);
436 	return (err);
437 }
438 
439 /*
440  * krb5ssp_ctx_destroy
441  *
442  * Destroy mechanism-specific data.
443  */
444 void
445 krb5ssp_destroy(struct ssp_ctx *sp)
446 {
447 	krb5ssp_state_t *ss;
448 	krb5_context	kctx;
449 
450 	ss = sp->sp_private;
451 	if (ss == NULL)
452 		return;
453 	sp->sp_private = NULL;
454 
455 	if ((kctx = ss->ss_krb5ctx) != NULL) {
456 		/* from krb5ssp_get_tkt */
457 		if (ss->ss_auth)
458 			(void) krb5_auth_con_free(kctx, ss->ss_auth);
459 		/* from krb5ssp_init_client */
460 		if (ss->ss_krb5clp)
461 			krb5_free_principal(kctx, ss->ss_krb5clp);
462 		if (ss->ss_krb5cc)
463 			(void) krb5_cc_close(kctx, ss->ss_krb5cc);
464 		krb5_free_context(kctx);
465 	}
466 
467 	free(ss);
468 }
469 
470 /*
471  * krb5ssp_init_clnt
472  *
473  * Initialize a new Kerberos SSP client context.
474  *
475  * The user must already have a TGT in their credential cache,
476  * as shown by the "klist" command.
477  */
478 int
479 krb5ssp_init_client(struct ssp_ctx *sp)
480 {
481 	krb5ssp_state_t *ss;
482 	krb5_error_code	kerr;
483 	krb5_context	kctx = NULL;
484 	krb5_ccache 	kcc = NULL;
485 	krb5_principal	kprin = NULL;
486 
487 	if ((sp->smb_ctx->ct_authflags & SMB_AT_KRB5) == 0) {
488 		DPRINT("KRB5 not in authflags");
489 		return (ENOTSUP);
490 	}
491 
492 	ss = calloc(1, sizeof (*ss));
493 	if (ss == NULL)
494 		return (ENOMEM);
495 
496 	sp->sp_nexttok = krb5ssp_next_token;
497 	sp->sp_destroy = krb5ssp_destroy;
498 	sp->sp_private = ss;
499 
500 	kerr = krb5_init_context(&kctx);
501 	if (kerr) {
502 		DPRINT("krb5_init_context, kerr 0x%x", kerr);
503 		goto errout;
504 	}
505 	ss->ss_krb5ctx = kctx;
506 
507 	/* non-default would instead use krb5_cc_resolve */
508 	kerr = krb5_cc_default(kctx, &kcc);
509 	if (kerr) {
510 		DPRINT("krb5_cc_default, kerr 0x%x", kerr);
511 		goto errout;
512 	}
513 	ss->ss_krb5cc = kcc;
514 
515 	/*
516 	 * Get the client principal (ticket),
517 	 * or discover that we don't have one.
518 	 */
519 	kerr = krb5_cc_get_principal(kctx, kcc, &kprin);
520 	if (kerr) {
521 		DPRINT("krb5_cc_get_principal, kerr 0x%x", kerr);
522 		goto errout;
523 	}
524 	ss->ss_krb5clp = kprin;
525 
526 	/* Success! */
527 	DPRINT("Ticket cache: %s:%s",
528 	    krb5_cc_get_type(kctx, kcc),
529 	    krb5_cc_get_name(kctx, kcc));
530 	return (0);
531 
532 errout:
533 	krb5ssp_destroy(sp);
534 	return (ENOTSUP);
535 }
536