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, >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 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