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
krb5ssp_tkt2gtok(uchar_t * tkt,ulong_t tktlen,uchar_t ** gtokp,ulong_t * gtoklenp)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
krb5ssp_get_tkt(krb5ssp_state_t * ss,char * server,uchar_t ** tktp,ulong_t * tktlenp)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
krb5ssp_put_request(struct ssp_ctx * sp,struct mbdata * out_mb)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, >ok, >oklen)) != 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
krb5ssp_get_reply(struct ssp_ctx * sp,struct mbdata * in_mb)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
krb5ssp_final(struct ssp_ctx * sp)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
krb5ssp_next_token(struct ssp_ctx * sp,struct mbdata * in_mb,struct mbdata * out_mb)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
krb5ssp_destroy(struct ssp_ctx * sp)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
krb5ssp_init_client(struct ssp_ctx * sp)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