1 /*
   2  * This file and its contents are supplied under the terms of the
   3  * Common Development and Distribution License ("CDDL"), version 1.0.
   4  * You may only use this file in accordance with the terms of version
   5  * 1.0 of the CDDL.
   6  *
   7  * A full copy of the text of the CDDL should have accompanied this
   8  * source.  A copy of the CDDL is also available via the Internet at
   9  * http://www.illumos.org/license/CDDL.
  10  */
  11 
  12 /*
  13  * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  14  */
  15 
  16 /*
  17  * SPNEGO back-end for Kerberos.  See [MS-KILE]
  18  */
  19 
  20 #include <sys/types.h>
  21 #include <gssapi/gssapi_ext.h>
  22 #include <gssapi/gssapi_krb5.h>
  23 #include <krb5.h>
  24 #include "smbd.h"
  25 #include "smbd_authsvc.h"
  26 
  27 /* From krb5/krb/pac.c (should have been exported) */
  28 #define PAC_LOGON_INFO          1
  29 
  30 typedef struct krb5ssp_backend {
  31         gss_ctx_id_t            be_gssctx;
  32         char                    *be_username;
  33         gss_buffer_desc         be_authz_pac;
  34         krb5_context            be_kctx;
  35         krb5_pac                be_kpac;
  36         krb5_data               be_pac;
  37 } krb5ssp_backend_t;
  38 
  39 static uint32_t
  40 get_authz_data_pac(
  41         gss_ctx_id_t context_handle,
  42         gss_buffer_t ad_data);
  43 
  44 static uint32_t
  45 get_ssnkey(authsvc_context_t *ctx);
  46 
  47 
  48 /*
  49  * Initialize this context for Kerberos, if possible.
  50  *
  51  * Should not get here unless libsmb smb_config_get_negtok
  52  * includes the Kerberos5 Mech OIDs in our spnego hint.
  53  *
  54  * Todo: allocate ctx->ctx_backend
  55  * See: krb5_gss_accept_sec_context()
  56  */
  57 int
  58 smbd_krb5ssp_init(authsvc_context_t *ctx)
  59 {
  60         krb5ssp_backend_t *be;
  61 
  62         be = malloc(sizeof (*be));
  63         if (be == 0)
  64                 return (NT_STATUS_NO_MEMORY);
  65         bzero(be, sizeof (*be));
  66         be->be_gssctx = GSS_C_NO_CONTEXT;
  67         ctx->ctx_backend = be;
  68 
  69         return (0);
  70 }
  71 
  72 /*
  73  * Todo: free ctx->ctx_backend
  74  */
  75 void
  76 smbd_krb5ssp_fini(authsvc_context_t *ctx)
  77 {
  78         krb5ssp_backend_t *be = ctx->ctx_backend;
  79         uint32_t minor;
  80 
  81         if (be == NULL)
  82                 return;
  83 
  84         if (be->be_kctx != NULL) {
  85                 krb5_free_data_contents(be->be_kctx, &be->be_pac);
  86 
  87                 if (be->be_kpac != NULL)
  88                         krb5_pac_free(be->be_kctx, be->be_kpac);
  89 
  90                 krb5_free_context(be->be_kctx);
  91         }
  92 
  93         (void) gss_release_buffer(NULL, &be->be_authz_pac);
  94 
  95         free(be->be_username);
  96 
  97         if (be->be_gssctx != GSS_C_NO_CONTEXT) {
  98                 (void) gss_delete_sec_context(&minor, &be->be_gssctx,
  99                     GSS_C_NO_BUFFER);
 100         }
 101 
 102         free(be);
 103 }
 104 
 105 /*
 106  * Handle a Kerberos auth message.
 107  *
 108  * State across messages is in ctx->ctx_backend
 109  */
 110 int
 111 smbd_krb5ssp_work(authsvc_context_t *ctx)
 112 {
 113         gss_buffer_desc intok, outtok;
 114         gss_buffer_desc namebuf;
 115         krb5ssp_backend_t *be = ctx->ctx_backend;
 116         gss_name_t gname = NULL;
 117         OM_uint32 major, minor, ret_flags;
 118         gss_OID name_type = GSS_C_NULL_OID;
 119         gss_OID mech_type = GSS_C_NULL_OID;
 120         krb5_error_code kerr;
 121         uint32_t status;
 122         smb_token_t *token = NULL;
 123 
 124         intok.length = ctx->ctx_ibodylen;
 125         intok.value  = ctx->ctx_ibodybuf;
 126         bzero(&outtok, sizeof (gss_buffer_desc));
 127         bzero(&namebuf, sizeof (gss_buffer_desc));
 128 
 129         /* Do this early, for error message support. */
 130         kerr = krb5_init_context(&be->be_kctx);
 131         if (kerr != 0) {
 132                 smbd_report("krb5ssp, krb5_init_ctx: %s",
 133                     krb5_get_error_message(be->be_kctx, kerr));
 134                 return (NT_STATUS_INTERNAL_ERROR);
 135         }
 136 
 137         free(be->be_username);
 138         be->be_username = NULL;
 139 
 140         major = gss_accept_sec_context(&minor, &be->be_gssctx,
 141             GSS_C_NO_CREDENTIAL, &intok,
 142             GSS_C_NO_CHANNEL_BINDINGS, &gname, &mech_type, &outtok,
 143             &ret_flags, NULL, NULL);
 144 
 145         if (outtok.length == 0)
 146                 ctx->ctx_obodylen = 0;
 147         else if (outtok.length <= ctx->ctx_obodylen) {
 148                 ctx->ctx_obodylen = outtok.length;
 149                 (void) memcpy(ctx->ctx_obodybuf, outtok.value, outtok.length);
 150                 free(outtok.value);
 151                 outtok.value = NULL;
 152         } else {
 153                 free(ctx->ctx_obodybuf);
 154                 ctx->ctx_obodybuf = outtok.value;
 155                 ctx->ctx_obodylen = outtok.length;
 156                 outtok.value = NULL;
 157         }
 158 
 159         if (GSS_ERROR(major)) {
 160                 smbd_report("krb5ssp: gss_accept_sec_context, "
 161                     "mech=0x%x, major=0x%x, minor=0x%x",
 162                     (int)mech_type, major, minor);
 163                 smbd_report(" krb5: %s",
 164                     krb5_get_error_message(be->be_kctx, minor));
 165                 status = NT_STATUS_WRONG_PASSWORD;
 166                 goto out;
 167         }
 168 
 169         switch (major) {
 170         case GSS_S_COMPLETE:
 171                 break;
 172         case GSS_S_CONTINUE_NEEDED:
 173                 if (outtok.length > 0) {
 174                         ctx->ctx_orawtype = LSA_MTYPE_ES_CONT;
 175                         /* becomes NT_STATUS_MORE_PROCESSING_REQUIRED */
 176                         return (0);
 177                 }
 178                 status = NT_STATUS_WRONG_PASSWORD;
 179                 goto out;
 180         default:
 181                 status = NT_STATUS_WRONG_PASSWORD;
 182                 goto out;
 183         }
 184 
 185         /*
 186          * OK, we got GSS_S_COMPLETE.  Get the name so we can use it
 187          * in log messages if we get failures decoding the PAC etc.
 188          * Then get the PAC, decode it, build the logon token.
 189          */
 190 
 191         if (gname != NULL && GSS_S_COMPLETE ==
 192             gss_display_name(&minor, gname, &namebuf, &name_type)) {
 193                 /* Save the user name. */
 194                 be->be_username = strdup(namebuf.value);
 195                 (void) gss_release_buffer(&minor, &namebuf);
 196                 (void) gss_release_name(&minor, &gname);
 197                 if (be->be_username == NULL) {
 198                         return (NT_STATUS_NO_MEMORY);
 199                 }
 200         }
 201 
 202         /*
 203          * Extract the KRB5_AUTHDATA_WIN2K_PAC data.
 204          */
 205         status = get_authz_data_pac(be->be_gssctx,
 206             &be->be_authz_pac);
 207         if (status)
 208                 goto out;
 209 
 210         kerr = krb5_pac_parse(be->be_kctx, be->be_authz_pac.value,
 211             be->be_authz_pac.length, &be->be_kpac);
 212         if (kerr) {
 213                 smbd_report("krb5ssp, krb5_pac_parse: %s",
 214                     krb5_get_error_message(be->be_kctx, kerr));
 215                 status = NT_STATUS_UNSUCCESSFUL;
 216                 goto out;
 217         }
 218 
 219         kerr = krb5_pac_get_buffer(be->be_kctx, be->be_kpac,
 220             PAC_LOGON_INFO, &be->be_pac);
 221         if (kerr) {
 222                 smbd_report("krb5ssp, krb5_pac_get_buffer: %s",
 223                     krb5_get_error_message(be->be_kctx, kerr));
 224                 status = NT_STATUS_UNSUCCESSFUL;
 225                 goto out;
 226         }
 227 
 228         ctx->ctx_token = calloc(1, sizeof (smb_token_t));
 229         if (ctx->ctx_token == NULL) {
 230                 status = NT_STATUS_NO_MEMORY;
 231                 goto out;
 232         }
 233         status = smb_decode_krb5_pac(ctx->ctx_token, be->be_pac.data,
 234             be->be_pac.length);
 235         if (status)
 236                 goto out;
 237 
 238         status = get_ssnkey(ctx);
 239         if (status)
 240                 goto out;
 241 
 242         if (!smb_token_setup_common(ctx->ctx_token)) {
 243                 status = NT_STATUS_UNSUCCESSFUL;
 244                 goto out;
 245         }
 246 
 247         /* Success! */
 248         ctx->ctx_orawtype = LSA_MTYPE_ES_DONE;
 249 
 250         status = 0;
 251         token = ctx->ctx_token;
 252 
 253         /*
 254          * Before we return, audit successful and failed logons.
 255          * We only audit logons where we should have a username.
 256          */
 257 out:
 258         if (!smbd_logon_audit(token, &ctx->ctx_clinfo.lci_clnt_ipaddr,
 259             be->be_username, "") && status == 0)
 260                 return (NT_STATUS_AUDIT_FAILED);
 261 
 262         return (status);
 263 }
 264 
 265 /*
 266  * See: GSS_KRB5_EXTRACT_AUTHZ_DATA_FROM_SEC_CONTEXT_OID
 267  * and: KRB5_AUTHDATA_WIN2K_PAC
 268  */
 269 static const gss_OID_desc
 270 oid_ex_authz_data_pac = {
 271         13, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x0a\x81\x00" };
 272 
 273 /*
 274  * See: krb5_gss_inquire_sec_context_by_oid()
 275  * and krb5_gss_inquire_sec_context_by_oid_ops[],
 276  * gss_krb5int_extract_authz_data_from_sec_context()
 277  */
 278 static uint32_t
 279 get_authz_data_pac(
 280         gss_ctx_id_t context_handle,
 281         gss_buffer_t ad_data)
 282 {
 283         gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET;
 284         OM_uint32 major, minor;
 285         uint32_t status = NT_STATUS_UNSUCCESSFUL;
 286 
 287         if (ad_data == NULL)
 288                 goto out;
 289 
 290         major = gss_inquire_sec_context_by_oid(
 291             &minor,
 292             context_handle,
 293             (gss_OID)&oid_ex_authz_data_pac,
 294             &data_set);
 295         if (GSS_ERROR(major)) {
 296                 smbd_report("krb5ssp, gss_inquire...PAC, "
 297                     "major=0x%x, minor=0x%x", major, minor);
 298                 goto out;
 299         }
 300 
 301         if ((data_set == GSS_C_NO_BUFFER_SET) || (data_set->count == 0)) {
 302                 goto out;
 303         }
 304 
 305         /* Only need the first element? */
 306         ad_data->length = data_set->elements[0].length;
 307         ad_data->value = malloc(ad_data->length);
 308         if (ad_data->value == NULL) {
 309                 status = NT_STATUS_NO_MEMORY;
 310                 goto out;
 311         }
 312         bcopy(data_set->elements[0].value, ad_data->value, ad_data->length);
 313         status = 0;
 314 
 315 out:
 316         (void) gss_release_buffer_set(&minor, &data_set);
 317 
 318         return (status);
 319 }
 320 
 321 /*
 322  * Get the session key, and save it in the token.
 323  *
 324  * See: krb5_gss_inquire_sec_context_by_oid(),
 325  * krb5_gss_inquire_sec_context_by_oid_ops[], and
 326  * gss_krb5int_inq_session_key
 327  */
 328 static uint32_t
 329 get_ssnkey(authsvc_context_t *ctx)
 330 {
 331         krb5ssp_backend_t *be = ctx->ctx_backend;
 332         gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET;
 333         OM_uint32 major, minor;
 334         size_t keylen;
 335         uint32_t status = NT_STATUS_UNSUCCESSFUL;
 336 
 337         major = gss_inquire_sec_context_by_oid(&minor,
 338             be->be_gssctx, GSS_C_INQ_SSPI_SESSION_KEY, &data_set);
 339         if (GSS_ERROR(major)) {
 340                 smbd_report("krb5ssp, failed to get session key, "
 341                     "major=0x%x, minor=0x%x", major, minor);
 342                 goto out;
 343         }
 344 
 345         /*
 346          * The key is in the first element
 347          */
 348         if (data_set == GSS_C_NO_BUFFER_SET ||
 349             data_set->count == 0 ||
 350             data_set->elements[0].length == 0 ||
 351             data_set->elements[0].value == NULL) {
 352                 smbd_report("krb5ssp: Session key is missing");
 353                 goto out;
 354         }
 355         if ((keylen = data_set->elements[0].length) < SMBAUTH_HASH_SZ) {
 356                 smbd_report("krb5ssp: Session key too short (%d)",
 357                     data_set->elements[0].length);
 358                 goto out;
 359         }
 360 
 361         ctx->ctx_token->tkn_ssnkey.val = malloc(keylen);
 362         if (ctx->ctx_token->tkn_ssnkey.val == NULL) {
 363                 status = NT_STATUS_NO_MEMORY;
 364                 goto out;
 365         }
 366         ctx->ctx_token->tkn_ssnkey.len = keylen;
 367         bcopy(data_set->elements[0].value,
 368             ctx->ctx_token->tkn_ssnkey.val, keylen);
 369         status = 0;
 370 
 371 out:
 372         (void) gss_release_buffer_set(&minor, &data_set);
 373         return (status);
 374 }