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 88 extern MECH_OID g_stcMechOIDList []; 89 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; 98 99 100 /* 101 * adds a GSSAPI wrapper 102 */ 103 int 104 krb5ssp_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 */ 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 152 }; 153 154 static 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 */ 161 static int 162 krb5ssp_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 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; 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 */ 249 int 250 krb5ssp_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, >ok, >oklen)) != 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 274 out: 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 */ 287 int 288 krb5ssp_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 365 out: 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 */ 380 int 381 krb5ssp_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 428 out: 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 */ 440 int 441 krb5ssp_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 460 out: 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 */ 471 void 472 krb5ssp_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 */ 505 int 506 krb5ssp_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 559 errout: 560 krb5ssp_destroy(sp); 561 return (ENOTSUP); 562 } 563