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 2015 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 
 123         intok.length = ctx->ctx_ibodylen;
 124         intok.value  = ctx->ctx_ibodybuf;
 125         bzero(&outtok, sizeof (gss_buffer_desc));
 126         bzero(&namebuf, sizeof (gss_buffer_desc));
 127 
 128         /* Do this early, for error message support. */
 129         kerr = krb5_init_context(&be->be_kctx);
 130         if (kerr != 0) {
 131                 smbd_report("krb5ssp, krb5_init_ctx: %s",
 132                     krb5_get_error_message(be->be_kctx, kerr));
 133                 return (NT_STATUS_INTERNAL_ERROR);
 134         }
 135 
 136         major = gss_accept_sec_context(&minor, &be->be_gssctx,
 137             GSS_C_NO_CREDENTIAL, &intok,
 138             GSS_C_NO_CHANNEL_BINDINGS, &gname, &mech_type, &outtok,
 139             &ret_flags, NULL, NULL);
 140 
 141         if (outtok.length == 0)
 142                 ctx->ctx_obodylen = 0;
 143         else if (outtok.length <= ctx->ctx_obodylen) {
 144                 ctx->ctx_obodylen = outtok.length;
 145                 (void) memcpy(ctx->ctx_obodybuf, outtok.value, outtok.length);
 146                 free(outtok.value);
 147                 outtok.value = NULL;
 148         } else {
 149                 free(ctx->ctx_obodybuf);
 150                 ctx->ctx_obodybuf = outtok.value;
 151                 ctx->ctx_obodylen = outtok.length;
 152                 outtok.value = NULL;
 153         }
 154 
 155         if (GSS_ERROR(major)) {
 156                 smbd_report("krb5ssp: gss_accept_sec_context, "
 157                     "mech=0x%x, major=0x%x, minor=0x%x",
 158                     (int)mech_type, major, minor);
 159                 smbd_report(" krb5: %s",
 160                     krb5_get_error_message(be->be_kctx, minor));
 161                 return (NT_STATUS_WRONG_PASSWORD);
 162         }
 163 
 164         switch (major) {
 165         case GSS_S_COMPLETE:
 166                 break;
 167         case GSS_S_CONTINUE_NEEDED:
 168                 if (outtok.length > 0) {
 169                         ctx->ctx_orawtype = LSA_MTYPE_ES_CONT;
 170                         /* becomes NT_STATUS_MORE_PROCESSING_REQUIRED */
 171                         return (0);
 172                 }
 173                 return (NT_STATUS_WRONG_PASSWORD);
 174         default:
 175                 return (NT_STATUS_WRONG_PASSWORD);
 176         }
 177 
 178         /*
 179          * OK, we got GSS_S_COMPLETE.  Get the name so we can use it
 180          * in log messages if we get failures decoding the PAC etc.
 181          * Then get the PAC, decode it, build the logon token.
 182          */
 183 
 184         if (gname != NULL && GSS_S_COMPLETE ==
 185             gss_display_name(&minor, gname, &namebuf, &name_type)) {
 186                 /* Save the user name. */
 187                 be->be_username = strdup(namebuf.value);
 188                 (void) gss_release_buffer(&minor, &namebuf);
 189                 (void) gss_release_name(&minor, &gname);
 190                 if (be->be_username == NULL) {
 191                         return (NT_STATUS_NO_MEMORY);
 192                 }
 193         }
 194 
 195         /*
 196          * Extract the KRB5_AUTHDATA_WIN2K_PAC data.
 197          */
 198         status = get_authz_data_pac(be->be_gssctx,
 199             &be->be_authz_pac);
 200         if (status)
 201                 return (status);
 202 
 203         kerr = krb5_pac_parse(be->be_kctx, be->be_authz_pac.value,
 204             be->be_authz_pac.length, &be->be_kpac);
 205         if (kerr) {
 206                 smbd_report("krb5ssp, krb5_pac_parse: %s",
 207                     krb5_get_error_message(be->be_kctx, kerr));
 208                 return (NT_STATUS_UNSUCCESSFUL);
 209         }
 210 
 211         kerr = krb5_pac_get_buffer(be->be_kctx, be->be_kpac,
 212             PAC_LOGON_INFO, &be->be_pac);
 213         if (kerr) {
 214                 smbd_report("krb5ssp, krb5_pac_get_buffer: %s",
 215                     krb5_get_error_message(be->be_kctx, kerr));
 216                 return (NT_STATUS_UNSUCCESSFUL);
 217         }
 218 
 219         ctx->ctx_token = calloc(1, sizeof (smb_token_t));
 220         if (ctx->ctx_token == NULL)
 221                 return (NT_STATUS_NO_MEMORY);
 222 
 223         status = smb_decode_krb5_pac(ctx->ctx_token, be->be_pac.data,
 224             be->be_pac.length);
 225         if (status)
 226                 return (status);
 227 
 228         status = get_ssnkey(ctx);
 229         if (status)
 230                 return (status);
 231 
 232         if (!smb_token_setup_common(ctx->ctx_token))
 233                 return (NT_STATUS_UNSUCCESSFUL);
 234 
 235         /* Success! */
 236         ctx->ctx_orawtype = LSA_MTYPE_ES_DONE;
 237 
 238         return (0);
 239 }
 240 
 241 /*
 242  * See: GSS_KRB5_EXTRACT_AUTHZ_DATA_FROM_SEC_CONTEXT_OID
 243  * and: KRB5_AUTHDATA_WIN2K_PAC
 244  */
 245 static const gss_OID_desc
 246 oid_ex_authz_data_pac = {
 247         13, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x0a\x81\x00" };
 248 
 249 /*
 250  * See: krb5_gss_inquire_sec_context_by_oid()
 251  * and krb5_gss_inquire_sec_context_by_oid_ops[],
 252  * gss_krb5int_extract_authz_data_from_sec_context()
 253  */
 254 static uint32_t
 255 get_authz_data_pac(
 256         gss_ctx_id_t context_handle,
 257         gss_buffer_t ad_data)
 258 {
 259         gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET;
 260         OM_uint32 major, minor;
 261         uint32_t status = NT_STATUS_UNSUCCESSFUL;
 262 
 263         if (ad_data == NULL)
 264                 goto out;
 265 
 266         major = gss_inquire_sec_context_by_oid(
 267             &minor,
 268             context_handle,
 269             (gss_OID)&oid_ex_authz_data_pac,
 270             &data_set);
 271         if (GSS_ERROR(major)) {
 272                 smbd_report("krb5ssp, gss_inquire...PAC, "
 273                     "major=0x%x, minor=0x%x", major, minor);
 274                 goto out;
 275         }
 276 
 277         if ((data_set == GSS_C_NO_BUFFER_SET) || (data_set->count == 0)) {
 278                 goto out;
 279         }
 280 
 281         /* Only need the first element? */
 282         ad_data->length = data_set->elements[0].length;
 283         ad_data->value = malloc(ad_data->length);
 284         if (ad_data->value == NULL) {
 285                 status = NT_STATUS_NO_MEMORY;
 286                 goto out;
 287         }
 288         bcopy(data_set->elements[0].value, ad_data->value, ad_data->length);
 289         status = 0;
 290 
 291 out:
 292         (void) gss_release_buffer_set(&minor, &data_set);
 293 
 294         return (status);
 295 }
 296 
 297 /*
 298  * Get the session key, and save it in the token.
 299  *
 300  * See: krb5_gss_inquire_sec_context_by_oid(),
 301  * krb5_gss_inquire_sec_context_by_oid_ops[], and
 302  * gss_krb5int_inq_session_key
 303  */
 304 static uint32_t
 305 get_ssnkey(authsvc_context_t *ctx)
 306 {
 307         krb5ssp_backend_t *be = ctx->ctx_backend;
 308         gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET;
 309         OM_uint32 major, minor;
 310         size_t keylen;
 311         uint32_t status = NT_STATUS_UNSUCCESSFUL;
 312 
 313         major = gss_inquire_sec_context_by_oid(&minor,
 314             be->be_gssctx, GSS_C_INQ_SSPI_SESSION_KEY, &data_set);
 315         if (GSS_ERROR(major)) {
 316                 smbd_report("krb5ssp, failed to get session key, "
 317                     "major=0x%x, minor=0x%x", major, minor);
 318                 goto out;
 319         }
 320 
 321         /*
 322          * The key is in the first element
 323          */
 324         if (data_set == GSS_C_NO_BUFFER_SET ||
 325             data_set->count == 0 ||
 326             data_set->elements[0].length == 0 ||
 327             data_set->elements[0].value == NULL) {
 328                 smbd_report("krb5ssp: Session key is missing");
 329                 goto out;
 330         }
 331         if ((keylen = data_set->elements[0].length) < SMBAUTH_HASH_SZ) {
 332                 smbd_report("krb5ssp: Session key too short (%d)",
 333                     data_set->elements[0].length);
 334                 goto out;
 335         }
 336 
 337         ctx->ctx_token->tkn_ssnkey.val = malloc(keylen);
 338         if (ctx->ctx_token->tkn_ssnkey.val == NULL) {
 339                 status = NT_STATUS_NO_MEMORY;
 340                 goto out;
 341         }
 342         ctx->ctx_token->tkn_ssnkey.len = keylen;
 343         bcopy(data_set->elements[0].value,
 344             ctx->ctx_token->tkn_ssnkey.val, keylen);
 345         status = 0;
 346 
 347 out:
 348         (void) gss_release_buffer_set(&minor, &data_set);
 349         return (status);
 350 }