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 } 351