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, &gtok, &gtoklen)) != 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 }