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