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