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