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
88extern MECH_OID g_stcMechOIDList [];
89
90typedef 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 */
103int
104krb5ssp_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 */
147static 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
154static 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 */
161static int
162krb5ssp_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
225out:
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 */
249int
250krb5ssp_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
274out:
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 */
287int
288krb5ssp_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
365out:
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 */
380int
381krb5ssp_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
428out:
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 */
440int
441krb5ssp_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
460out:
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 */
471void
472krb5ssp_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 */
505int
506krb5ssp_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
559errout:
560	krb5ssp_destroy(sp);
561	return (ENOTSUP);
562}
563