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