import less(1)
[unleashed/tickless.git] / usr / src / lib / libsmbfs / smb / krb5ssp.c
blobd76644f947a57999fd368bfe3a689dc7dbd96e2d
1 /*
2 * Copyright (c) 2000, Boris Popov
3 * All rights reserved.
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.
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.
34 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
38 * Kerberos V Security Support Provider
40 * Based on code previously in ctx.c (from Boris Popov?)
41 * but then mostly rewritten at Sun.
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>
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>
61 #include <netinet/in.h>
62 #include <netinet/tcp.h>
63 #include <arpa/inet.h>
65 #include <netsmb/smb.h>
66 #include <netsmb/smb_lib.h>
67 #include <netsmb/mchain.h>
69 #include "private.h"
70 #include "charsets.h"
71 #include "spnego.h"
72 #include "derparse.h"
73 #include "ssp.h"
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>
80 /* RFC 4121 checksum type ID. */
81 #define CKSUM_TYPE_RFC4121 0x8003
83 /* RFC 1964 token ID codes */
84 #define KRB_AP_REQ 1
85 #define KRB_AP_REP 2
86 #define KRB_ERROR 3
88 extern MECH_OID g_stcMechOIDList [];
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;
101 * adds a GSSAPI wrapper
104 krb5ssp_tkt2gtok(uchar_t *tkt, ulong_t tktlen,
105 uchar_t **gtokp, ulong_t *gtoklenp)
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;
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);
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);
128 assert(b + tktlen == blob + bloblen);
129 memcpy(b, tkt, tktlen);
130 *gtoklenp = bloblen;
131 *gtokp = blob;
132 return (0);
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.)
141 * Only session enc type should matter, not ticket enc type,
142 * per Sam Hartman on krbdev.
144 * Preauthentication failure topics in krb-protocol may help here...
145 * try "John Brezak" and/or "Clifford Neuman" too.
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
154 static const int rq_opts =
155 AP_OPTS_USE_SUBKEY | AP_OPTS_MUTUAL_REQUIRED;
158 * Obtain a kerberos ticket for the host we're connecting to.
159 * (This does the KRB_TGS exchange.)
161 static int
162 krb5ssp_get_tkt(krb5ssp_state_t *ss, char *server,
163 uchar_t **tktp, ulong_t *tktlenp)
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;
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;
180 kerr = krb5_set_default_tgs_enctypes(kctx, kenctypes);
181 if (kerr != 0) {
182 fn = "krb5_set_default_tgs_enctypes";
183 goto out;
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;
192 /* Missing krb5_auth_con_set_req_cksumtype(), so inline. */
193 ss->ss_auth->req_cksumtype = CKSUM_TYPE_RFC4121;
196 * Build an RFC 4121 "checksum" with NULL channel bindings,
197 * like make_gss_checksum(). Numbers here from the RFC.
199 indata.length = 24;
200 if ((indata.data = calloc(1, indata.length)) == NULL) {
201 kerr = ENOMEM;
202 fn = "malloc checksum";
203 goto out;
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". */
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;
215 if ((tkt = malloc(outdata.length)) == NULL) {
216 kerr = ENOMEM;
217 fn = "malloc signing key";
218 goto out;
220 memcpy(tkt, outdata.data, outdata.length);
221 *tktp = tkt;
222 *tktlenp = outdata.length;
223 kerr = 0;
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;
234 if (outdata.data)
235 krb5_free_data_contents(kctx, &outdata);
237 free(indata.data);
239 /* Free kctx in krb5ssp_destroy */
240 return (kerr);
245 * Build an RFC 1964 KRB_AP_REQ message
246 * The caller puts on the SPNEGO wrapper.
249 krb5ssp_put_request(struct ssp_ctx *sp, struct mbdata *out_mb)
251 int err;
252 struct smb_ctx *ctx = sp->smb_ctx;
253 krb5ssp_state_t *ss = sp->sp_private;
254 uchar_t *tkt = NULL;
255 ulong_t tktlen;
256 uchar_t *gtok = NULL; /* gssapi token */
257 ulong_t gtoklen; /* gssapi token length */
258 char *prin = ctx->ct_srvname;
260 if ((err = krb5ssp_get_tkt(ss, prin, &tkt, &tktlen)) != 0)
261 goto out;
262 if ((err = krb5ssp_tkt2gtok(tkt, tktlen, &gtok, &gtoklen)) != 0)
263 goto out;
265 if ((err = mb_init_sz(out_mb, gtoklen)) != 0)
266 goto out;
267 if ((err = mb_put_mem(out_mb, gtok, gtoklen, MB_MSYSTEM)) != 0)
268 goto out;
270 if (ctx->ct_vcflags & SMBV_WILL_SIGN)
271 ctx->ct_hflags2 |= SMB_FLAGS2_SECURITY_SIGNATURE;
273 out:
274 free(gtok);
275 free(tkt);
277 return (err);
281 * Unwrap a GSS-API encapsulated RFC 1964 reply message,
282 * i.e. type KRB_AP_REP or KRB_ERROR.
285 krb5ssp_get_reply(struct ssp_ctx *sp, struct mbdata *in_mb)
287 krb5ssp_state_t *ss = sp->sp_private;
288 mbuf_t *m = in_mb->mb_top;
289 int err = EBADRPC;
290 int dlen, rc;
291 long actual_len, token_len;
292 uchar_t *data;
293 krb5_data ap = {0};
294 krb5_ap_rep_enc_part *reply = NULL;
296 /* cheating: this mbuf is contiguous */
297 assert(m->m_data == in_mb->mb_pos);
298 data = (uchar_t *)m->m_data;
299 dlen = m->m_len;
302 * Peel off the GSS-API wrapper. Looks like:
303 * AppToken: 60 81 83
304 * OID(KRB5): 06 09 2a 86 48 86 f7 12 01 02 02
305 * KRB_AP_REP: 02 00
307 rc = ASNDerCheckToken(data, SPNEGO_NEGINIT_APP_CONSTRUCT,
308 0, dlen, &token_len, &actual_len);
309 if (rc != SPNEGO_E_SUCCESS) {
310 DPRINT("no AppToken? rc=0x%x", rc);
311 goto out;
313 if (dlen < actual_len)
314 goto out;
315 data += actual_len;
316 dlen -= actual_len;
318 /* OID (KRB5) */
319 rc = ASNDerCheckOID(data, spnego_mech_oid_Kerberos_V5,
320 dlen, &actual_len);
321 if (rc != SPNEGO_E_SUCCESS) {
322 DPRINT("no OID? rc=0x%x", rc);
323 goto out;
325 if (dlen < actual_len)
326 goto out;
327 data += actual_len;
328 dlen -= actual_len;
330 /* KRB_AP_REP or KRB_ERROR */
331 if (data[0] != KRB_AP_REP) {
332 DPRINT("KRB5 type: %d", data[1]);
333 goto out;
335 if (dlen < 2)
336 goto out;
337 data += 2;
338 dlen -= 2;
341 * Now what's left should be a krb5 reply
342 * NB: ap is NOT allocated, so don't free it.
344 ap.length = dlen;
345 ap.data = (char *)data;
346 rc = krb5_rd_rep(ss->ss_krb5ctx, ss->ss_auth, &ap, &reply);
347 if (rc != 0) {
348 DPRINT("krb5_rd_rep: err 0x%x (%s)",
349 rc, error_message(rc));
350 err = EAUTH;
351 goto out;
355 * Have the decoded reply. Save anything?
357 * NB: If this returns an error, we will get
358 * no more calls into this back-end module.
360 err = 0;
362 out:
363 if (reply != NULL)
364 krb5_free_ap_rep_enc_part(ss->ss_krb5ctx, reply);
365 if (err)
366 DPRINT("ret %d", err);
368 return (err);
372 * krb5ssp_final
374 * Called after successful authentication.
375 * Setup the MAC key for signing.
378 krb5ssp_final(struct ssp_ctx *sp)
380 struct smb_ctx *ctx = sp->smb_ctx;
381 krb5ssp_state_t *ss = sp->sp_private;
382 krb5_keyblock *ssn_key = NULL;
383 int err, len;
386 * Save the session key, used for SMB signing
387 * and possibly other consumers (RPC).
389 err = krb5_auth_con_getlocalsubkey(
390 ss->ss_krb5ctx, ss->ss_auth, &ssn_key);
391 if (err != 0) {
392 DPRINT("_getlocalsubkey, err=0x%x (%s)",
393 err, error_message(err));
394 if (err <= 0 || err > ESTALE)
395 err = EAUTH;
396 goto out;
398 memset(ctx->ct_ssn_key, 0, SMBIOC_HASH_SZ);
399 if ((len = ssn_key->length) > SMBIOC_HASH_SZ)
400 len = SMBIOC_HASH_SZ;
401 memcpy(ctx->ct_ssn_key, ssn_key->contents, len);
404 * Set the MAC key on the first successful auth.
406 if ((ctx->ct_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) &&
407 (ctx->ct_mackey == NULL)) {
408 ctx->ct_mackeylen = ssn_key->length;
409 ctx->ct_mackey = malloc(ctx->ct_mackeylen);
410 if (ctx->ct_mackey == NULL) {
411 ctx->ct_mackeylen = 0;
412 err = ENOMEM;
413 goto out;
415 memcpy(ctx->ct_mackey, ssn_key->contents,
416 ctx->ct_mackeylen);
418 * Apparently, the server used seq. no. zero
419 * for our previous message, so next is two.
421 ctx->ct_mac_seqno = 2;
423 err = 0;
425 out:
426 if (ssn_key)
427 krb5_free_keyblock(ss->ss_krb5ctx, ssn_key);
429 return (err);
433 * krb5ssp_next_token
435 * See ssp.c: ssp_ctx_next_token
438 krb5ssp_next_token(struct ssp_ctx *sp, struct mbdata *in_mb,
439 struct mbdata *out_mb)
441 int err;
444 * Note: in_mb == NULL on the first call.
446 if (in_mb) {
447 err = krb5ssp_get_reply(sp, in_mb);
448 if (err)
449 goto out;
452 if (out_mb) {
453 err = krb5ssp_put_request(sp, out_mb);
454 } else
455 err = krb5ssp_final(sp);
457 out:
458 if (err)
459 DPRINT("ret: %d", err);
460 return (err);
464 * krb5ssp_ctx_destroy
466 * Destroy mechanism-specific data.
468 void
469 krb5ssp_destroy(struct ssp_ctx *sp)
471 krb5ssp_state_t *ss;
472 krb5_context kctx;
474 ss = sp->sp_private;
475 if (ss == NULL)
476 return;
477 sp->sp_private = NULL;
479 if ((kctx = ss->ss_krb5ctx) != NULL) {
480 /* from krb5ssp_get_tkt */
481 if (ss->ss_auth)
482 (void) krb5_auth_con_free(kctx, ss->ss_auth);
483 /* from krb5ssp_init_client */
484 if (ss->ss_krb5clp)
485 krb5_free_principal(kctx, ss->ss_krb5clp);
486 if (ss->ss_krb5cc)
487 (void) krb5_cc_close(kctx, ss->ss_krb5cc);
488 krb5_free_context(kctx);
491 free(ss);
495 * krb5ssp_init_clnt
497 * Initialize a new Kerberos SSP client context.
499 * The user must already have a TGT in their credential cache,
500 * as shown by the "klist" command.
503 krb5ssp_init_client(struct ssp_ctx *sp)
505 krb5ssp_state_t *ss;
506 krb5_error_code kerr;
507 krb5_context kctx = NULL;
508 krb5_ccache kcc = NULL;
509 krb5_principal kprin = NULL;
511 if ((sp->smb_ctx->ct_authflags & SMB_AT_KRB5) == 0) {
512 DPRINT("KRB5 not in authflags");
513 return (ENOTSUP);
516 ss = calloc(1, sizeof (*ss));
517 if (ss == NULL)
518 return (ENOMEM);
520 sp->sp_nexttok = krb5ssp_next_token;
521 sp->sp_destroy = krb5ssp_destroy;
522 sp->sp_private = ss;
524 kerr = krb5_init_context(&kctx);
525 if (kerr) {
526 DPRINT("krb5_init_context, kerr 0x%x", kerr);
527 goto errout;
529 ss->ss_krb5ctx = kctx;
531 /* non-default would instead use krb5_cc_resolve */
532 kerr = krb5_cc_default(kctx, &kcc);
533 if (kerr) {
534 DPRINT("krb5_cc_default, kerr 0x%x", kerr);
535 goto errout;
537 ss->ss_krb5cc = kcc;
540 * Get the client principal (ticket),
541 * or discover that we don't have one.
543 kerr = krb5_cc_get_principal(kctx, kcc, &kprin);
544 if (kerr) {
545 DPRINT("krb5_cc_get_principal, kerr 0x%x", kerr);
546 goto errout;
548 ss->ss_krb5clp = kprin;
550 /* Success! */
551 DPRINT("Ticket cache: %s:%s",
552 krb5_cc_get_type(kctx, kcc),
553 krb5_cc_get_name(kctx, kcc));
554 return (0);
556 errout:
557 krb5ssp_destroy(sp);
558 return (ENOTSUP);