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 2009 Sun Microsystems, Inc. All rights reserved. 35 * Use is subject to license terms. 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 79 /* RFC 1964 token ID codes */ 80 #define KRB_AP_REQ 1 81 #define KRB_AP_REP 2 82 #define KRB_ERROR 3 83 84 extern MECH_OID g_stcMechOIDList []; 85 86 typedef struct krb5ssp_state { 87 /* Filled in by krb5ssp_init_client */ 88 krb5_context ss_krb5ctx; /* krb5 context (ptr) */ 89 krb5_ccache ss_krb5cc; /* credentials cache (ptr) */ 90 krb5_principal ss_krb5clp; /* client principal (ptr) */ 91 /* Filled in by krb5ssp_get_tkt */ 92 krb5_auth_context ss_auth; /* auth ctx. w/ server (ptr) */ 93 } krb5ssp_state_t; 94 95 96 /* 97 * adds a GSSAPI wrapper 98 */ 99 int 100 krb5ssp_tkt2gtok(uchar_t *tkt, ulong_t tktlen, 101 uchar_t **gtokp, ulong_t *gtoklenp) 102 { 103 ulong_t len; 104 ulong_t bloblen = tktlen; 105 uchar_t krbapreq[2] = { KRB_AP_REQ, 0 }; 106 uchar_t *blob = NULL; /* result */ 107 uchar_t *b; 108 109 bloblen += sizeof (krbapreq); 110 bloblen += g_stcMechOIDList[spnego_mech_oid_Kerberos_V5].iLen; 111 len = bloblen; 112 bloblen = ASNDerCalcTokenLength(bloblen, bloblen); 113 if ((blob = malloc(bloblen)) == NULL) { 114 DPRINT("malloc"); 115 return (ENOMEM); 116 } 117 118 b = blob; 119 b += ASNDerWriteToken(b, SPNEGO_NEGINIT_APP_CONSTRUCT, NULL, len); 120 b += ASNDerWriteOID(b, spnego_mech_oid_Kerberos_V5); 121 memcpy(b, krbapreq, sizeof (krbapreq)); 122 b += sizeof (krbapreq); 123 124 assert(b + tktlen == blob + bloblen); 125 memcpy(b, tkt, tktlen); 126 *gtoklenp = bloblen; 127 *gtokp = blob; 128 return (0); 129 } 130 131 /* 132 * See "Windows 2000 Kerberos Interoperability" paper by 133 * Christopher Nebergall. RC4 HMAC is the W2K default but 134 * Samba support lagged (not due to Samba itself, but due to OS' 135 * Kerberos implementations.) 136 * 137 * Only session enc type should matter, not ticket enc type, 138 * per Sam Hartman on krbdev. 139 * 140 * Preauthentication failure topics in krb-protocol may help here... 141 * try "John Brezak" and/or "Clifford Neuman" too. 142 */ 143 static krb5_enctype kenctypes[] = { 144 ENCTYPE_ARCFOUR_HMAC, /* defined in krb5.h */ 145 ENCTYPE_DES_CBC_MD5, 146 ENCTYPE_DES_CBC_CRC, 147 ENCTYPE_NULL 148 }; 149 150 static const int rq_opts = 151 AP_OPTS_USE_SUBKEY | AP_OPTS_MUTUAL_REQUIRED; 152 153 /* 154 * Obtain a kerberos ticket for the host we're connecting to. 155 * (This does the KRB_TGS exchange.) 156 */ 157 static int 158 krb5ssp_get_tkt(krb5ssp_state_t *ss, char *server, 159 uchar_t **tktp, ulong_t *tktlenp) 160 { 161 krb5_context kctx = ss->ss_krb5ctx; 162 krb5_ccache kcc = ss->ss_krb5cc; 163 krb5_data indata = {0}; 164 krb5_data outdata = {0}; 165 krb5_error_code kerr = 0; 166 const char *fn = NULL; 167 uchar_t *tkt; 168 169 /* Should have these from krb5ssp_init_client. */ 170 if (kctx == NULL || kcc == NULL) { 171 fn = "null kctx or kcc"; 172 kerr = EINVAL; 173 goto out; 174 } 175 176 kerr = krb5_set_default_tgs_enctypes(kctx, kenctypes); 177 if (kerr != 0) { 178 fn = "krb5_set_default_tgs_enctypes"; 179 goto out; 180 } 181 182 /* Override the krb5 library default. */ 183 indata.data = ""; 184 185 kerr = krb5_mk_req(kctx, &ss->ss_auth, rq_opts, "cifs", server, 186 &indata, kcc, &outdata); 187 if (kerr != 0) { 188 fn = "krb5_mk_req"; 189 goto out; 190 } 191 if ((tkt = malloc(outdata.length)) == NULL) { 192 kerr = ENOMEM; 193 fn = "malloc signing key"; 194 goto out; 195 } 196 memcpy(tkt, outdata.data, outdata.length); 197 *tktp = tkt; 198 *tktlenp = outdata.length; 199 kerr = 0; 200 201 out: 202 if (kerr) { 203 if (fn == NULL) 204 fn = "?"; 205 DPRINT("%s err 0x%x: %s", fn, kerr, error_message(kerr)); 206 if (kerr <= 0 || kerr > ESTALE) 207 kerr = EAUTH; 208 } 209 210 if (outdata.data) 211 krb5_free_data_contents(kctx, &outdata); 212 213 /* Free kctx in krb5ssp_destroy */ 214 return (kerr); 215 } 216 217 218 /* 219 * Build an RFC 1964 KRB_AP_REQ message 220 * The caller puts on the SPNEGO wrapper. 221 */ 222 int 223 krb5ssp_put_request(struct ssp_ctx *sp, struct mbdata *out_mb) 224 { 225 int err; 226 struct smb_ctx *ctx = sp->smb_ctx; 227 krb5ssp_state_t *ss = sp->sp_private; 228 uchar_t *tkt = NULL; 229 ulong_t tktlen; 230 uchar_t *gtok = NULL; /* gssapi token */ 231 ulong_t gtoklen; /* gssapi token length */ 232 char *prin = ctx->ct_srvname; 233 234 if ((err = krb5ssp_get_tkt(ss, prin, &tkt, &tktlen)) != 0) 235 goto out; 236 if ((err = krb5ssp_tkt2gtok(tkt, tktlen, >ok, >oklen)) != 0) 237 goto out; 238 239 if ((err = mb_init_sz(out_mb, gtoklen)) != 0) 240 goto out; 241 if ((err = mb_put_mem(out_mb, gtok, gtoklen, MB_MSYSTEM)) != 0) 242 goto out; 243 244 if (ctx->ct_vcflags & SMBV_WILL_SIGN) 245 ctx->ct_hflags2 |= SMB_FLAGS2_SECURITY_SIGNATURE; 246 247 out: 248 if (gtok) 249 free(gtok); 250 if (tkt) 251 free(tkt); 252 253 return (err); 254 } 255 256 /* 257 * Unwrap a GSS-API encapsulated RFC 1964 reply message, 258 * i.e. type KRB_AP_REP or KRB_ERROR. 259 */ 260 int 261 krb5ssp_get_reply(struct ssp_ctx *sp, struct mbdata *in_mb) 262 { 263 krb5ssp_state_t *ss = sp->sp_private; 264 mbuf_t *m = in_mb->mb_top; 265 int err = EBADRPC; 266 int dlen, rc; 267 long actual_len, token_len; 268 uchar_t *data; 269 krb5_data ap = {0}; 270 krb5_ap_rep_enc_part *reply = NULL; 271 272 /* cheating: this mbuf is contiguous */ 273 assert(m->m_data == in_mb->mb_pos); 274 data = (uchar_t *)m->m_data; 275 dlen = m->m_len; 276 277 /* 278 * Peel off the GSS-API wrapper. Looks like: 279 * AppToken: 60 81 83 280 * OID(KRB5): 06 09 2a 86 48 86 f7 12 01 02 02 281 * KRB_AP_REP: 02 00 282 */ 283 rc = ASNDerCheckToken(data, SPNEGO_NEGINIT_APP_CONSTRUCT, 284 0, dlen, &token_len, &actual_len); 285 if (rc != SPNEGO_E_SUCCESS) { 286 DPRINT("no AppToken? rc=0x%x", rc); 287 goto out; 288 } 289 if (dlen < actual_len) 290 goto out; 291 data += actual_len; 292 dlen -= actual_len; 293 294 /* OID (KRB5) */ 295 rc = ASNDerCheckOID(data, spnego_mech_oid_Kerberos_V5, 296 dlen, &actual_len); 297 if (rc != SPNEGO_E_SUCCESS) { 298 DPRINT("no OID? rc=0x%x", rc); 299 goto out; 300 } 301 if (dlen < actual_len) 302 goto out; 303 data += actual_len; 304 dlen -= actual_len; 305 306 /* KRB_AP_REP or KRB_ERROR */ 307 if (data[0] != KRB_AP_REP) { 308 DPRINT("KRB5 type: %d", data[1]); 309 goto out; 310 } 311 if (dlen < 2) 312 goto out; 313 data += 2; 314 dlen -= 2; 315 316 /* 317 * Now what's left should be a krb5 reply 318 * NB: ap is NOT allocated, so don't free it. 319 */ 320 ap.length = dlen; 321 ap.data = (char *)data; 322 rc = krb5_rd_rep(ss->ss_krb5ctx, ss->ss_auth, &ap, &reply); 323 if (rc != 0) { 324 DPRINT("krb5_rd_rep: err 0x%x (%s)", 325 rc, error_message(rc)); 326 err = EAUTH; 327 goto out; 328 } 329 330 /* 331 * Have the decoded reply. Save anything? 332 * 333 * NB: If this returns an error, we will get 334 * no more calls into this back-end module. 335 */ 336 err = 0; 337 338 out: 339 if (reply != NULL) 340 krb5_free_ap_rep_enc_part(ss->ss_krb5ctx, reply); 341 if (err) 342 DPRINT("ret %d", err); 343 344 return (err); 345 } 346 347 /* 348 * krb5ssp_final 349 * 350 * Called after successful authentication. 351 * Setup the MAC key for signing. 352 */ 353 int 354 krb5ssp_final(struct ssp_ctx *sp) 355 { 356 struct smb_ctx *ctx = sp->smb_ctx; 357 krb5ssp_state_t *ss = sp->sp_private; 358 krb5_keyblock *ssn_key = NULL; 359 int err, len; 360 361 /* 362 * Save the session key, used for SMB signing 363 * and possibly other consumers (RPC). 364 */ 365 err = krb5_auth_con_getlocalsubkey( 366 ss->ss_krb5ctx, ss->ss_auth, &ssn_key); 367 if (err != 0) { 368 DPRINT("_getlocalsubkey, err=0x%x (%s)", 369 err, error_message(err)); 370 if (err <= 0 || err > ESTALE) 371 err = EAUTH; 372 goto out; 373 } 374 memset(ctx->ct_ssn_key, 0, SMBIOC_HASH_SZ); 375 if ((len = ssn_key->length) > SMBIOC_HASH_SZ) 376 len = SMBIOC_HASH_SZ; 377 memcpy(ctx->ct_ssn_key, ssn_key->contents, len); 378 379 /* 380 * Set the MAC key on the first successful auth. 381 */ 382 if ((ctx->ct_hflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) && 383 (ctx->ct_mackey == NULL)) { 384 ctx->ct_mackeylen = ssn_key->length; 385 ctx->ct_mackey = malloc(ctx->ct_mackeylen); 386 if (ctx->ct_mackey == NULL) { 387 ctx->ct_mackeylen = 0; 388 err = ENOMEM; 389 goto out; 390 } 391 memcpy(ctx->ct_mackey, ssn_key->contents, 392 ctx->ct_mackeylen); 393 /* 394 * Apparently, the server used seq. no. zero 395 * for our previous message, so next is two. 396 */ 397 ctx->ct_mac_seqno = 2; 398 } 399 err = 0; 400 401 out: 402 if (ssn_key) 403 krb5_free_keyblock(ss->ss_krb5ctx, ssn_key); 404 405 return (err); 406 } 407 408 /* 409 * krb5ssp_next_token 410 * 411 * See ssp.c: ssp_ctx_next_token 412 */ 413 int 414 krb5ssp_next_token(struct ssp_ctx *sp, struct mbdata *in_mb, 415 struct mbdata *out_mb) 416 { 417 int err; 418 419 /* 420 * Note: in_mb == NULL on the first call. 421 */ 422 if (in_mb) { 423 err = krb5ssp_get_reply(sp, in_mb); 424 if (err) 425 goto out; 426 } 427 428 if (out_mb) { 429 err = krb5ssp_put_request(sp, out_mb); 430 } else 431 err = krb5ssp_final(sp); 432 433 out: 434 if (err) 435 DPRINT("ret: %d", err); 436 return (err); 437 } 438 439 /* 440 * krb5ssp_ctx_destroy 441 * 442 * Destroy mechanism-specific data. 443 */ 444 void 445 krb5ssp_destroy(struct ssp_ctx *sp) 446 { 447 krb5ssp_state_t *ss; 448 krb5_context kctx; 449 450 ss = sp->sp_private; 451 if (ss == NULL) 452 return; 453 sp->sp_private = NULL; 454 455 if ((kctx = ss->ss_krb5ctx) != NULL) { 456 /* from krb5ssp_get_tkt */ 457 if (ss->ss_auth) 458 (void) krb5_auth_con_free(kctx, ss->ss_auth); 459 /* from krb5ssp_init_client */ 460 if (ss->ss_krb5clp) 461 krb5_free_principal(kctx, ss->ss_krb5clp); 462 if (ss->ss_krb5cc) 463 (void) krb5_cc_close(kctx, ss->ss_krb5cc); 464 krb5_free_context(kctx); 465 } 466 467 free(ss); 468 } 469 470 /* 471 * krb5ssp_init_clnt 472 * 473 * Initialize a new Kerberos SSP client context. 474 * 475 * The user must already have a TGT in their credential cache, 476 * as shown by the "klist" command. 477 */ 478 int 479 krb5ssp_init_client(struct ssp_ctx *sp) 480 { 481 krb5ssp_state_t *ss; 482 krb5_error_code kerr; 483 krb5_context kctx = NULL; 484 krb5_ccache kcc = NULL; 485 krb5_principal kprin = NULL; 486 487 if ((sp->smb_ctx->ct_authflags & SMB_AT_KRB5) == 0) { 488 DPRINT("KRB5 not in authflags"); 489 return (ENOTSUP); 490 } 491 492 ss = calloc(1, sizeof (*ss)); 493 if (ss == NULL) 494 return (ENOMEM); 495 496 sp->sp_nexttok = krb5ssp_next_token; 497 sp->sp_destroy = krb5ssp_destroy; 498 sp->sp_private = ss; 499 500 kerr = krb5_init_context(&kctx); 501 if (kerr) { 502 DPRINT("krb5_init_context, kerr 0x%x", kerr); 503 goto errout; 504 } 505 ss->ss_krb5ctx = kctx; 506 507 /* non-default would instead use krb5_cc_resolve */ 508 kerr = krb5_cc_default(kctx, &kcc); 509 if (kerr) { 510 DPRINT("krb5_cc_default, kerr 0x%x", kerr); 511 goto errout; 512 } 513 ss->ss_krb5cc = kcc; 514 515 /* 516 * Get the client principal (ticket), 517 * or discover that we don't have one. 518 */ 519 kerr = krb5_cc_get_principal(kctx, kcc, &kprin); 520 if (kerr) { 521 DPRINT("krb5_cc_get_principal, kerr 0x%x", kerr); 522 goto errout; 523 } 524 ss->ss_krb5clp = kprin; 525 526 /* Success! */ 527 DPRINT("Ticket cache: %s:%s", 528 krb5_cc_get_type(kctx, kcc), 529 krb5_cc_get_name(kctx, kcc)); 530 return (0); 531 532 errout: 533 krb5ssp_destroy(sp); 534 return (ENOTSUP); 535 } 536