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 2022 Tintri by DDN, 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 static char *krb5ssp_def_username = "Unknown-Kerberos-User"; 106 static char *krb5ssp_def_domain = "Unknown-Domain"; 107 108 /* 109 * Handle a Kerberos auth message. 110 * 111 * State across messages is in ctx->ctx_backend. 112 * 113 * Equivalent to smbd_user_auth_logon(). 114 */ 115 int 116 smbd_krb5ssp_work(authsvc_context_t *ctx) 117 { 118 gss_buffer_desc intok, outtok; 119 gss_buffer_desc namebuf; 120 krb5ssp_backend_t *be = ctx->ctx_backend; 121 gss_name_t gname = NULL; 122 OM_uint32 major, minor, ret_flags; 123 gss_OID name_type = GSS_C_NULL_OID; 124 gss_OID mech_type = GSS_C_NULL_OID; 125 krb5_error_code kerr; 126 uint32_t status; 127 smb_token_t *token = NULL; 128 129 intok.length = ctx->ctx_ibodylen; 130 intok.value = ctx->ctx_ibodybuf; 131 bzero(&outtok, sizeof (gss_buffer_desc)); 132 bzero(&namebuf, sizeof (gss_buffer_desc)); 133 134 assert(be->be_username == NULL); 135 136 /* Do this early, for error message support. */ 137 kerr = krb5_init_context(&be->be_kctx); 138 if (kerr != 0) { 139 smbd_report("krb5ssp, krb5_init_ctx: %s", 140 krb5_get_error_message(be->be_kctx, kerr)); 141 return (NT_STATUS_INTERNAL_ERROR); 142 } 143 144 major = gss_accept_sec_context(&minor, &be->be_gssctx, 145 GSS_C_NO_CREDENTIAL, &intok, 146 GSS_C_NO_CHANNEL_BINDINGS, &gname, &mech_type, &outtok, 147 &ret_flags, NULL, NULL); 148 149 if (outtok.length == 0) 150 ctx->ctx_obodylen = 0; 151 else if (outtok.length <= ctx->ctx_obodylen) { 152 ctx->ctx_obodylen = outtok.length; 153 (void) memcpy(ctx->ctx_obodybuf, outtok.value, outtok.length); 154 free(outtok.value); 155 outtok.value = NULL; 156 } else { 157 free(ctx->ctx_obodybuf); 158 ctx->ctx_obodybuf = outtok.value; 159 ctx->ctx_obodylen = outtok.length; 160 outtok.value = NULL; 161 } 162 163 if (GSS_ERROR(major)) { 164 smbd_report("krb5ssp: gss_accept_sec_context, " 165 "mech=0x%x, major=0x%x, minor=0x%x", 166 (int)mech_type, major, minor); 167 smbd_report(" krb5: %s", 168 krb5_get_error_message(be->be_kctx, minor)); 169 status = NT_STATUS_WRONG_PASSWORD; 170 goto out; 171 } 172 173 switch (major) { 174 case GSS_S_COMPLETE: 175 break; 176 case GSS_S_CONTINUE_NEEDED: 177 if (outtok.length > 0) { 178 ctx->ctx_orawtype = LSA_MTYPE_ES_CONT; 179 /* becomes NT_STATUS_MORE_PROCESSING_REQUIRED */ 180 return (0); 181 } 182 status = NT_STATUS_WRONG_PASSWORD; 183 goto out; 184 default: 185 status = NT_STATUS_WRONG_PASSWORD; 186 goto out; 187 } 188 189 /* 190 * OK, we got GSS_S_COMPLETE. Get the name so we can use it 191 * in log messages if we get failures decoding the PAC etc. 192 * Then get the PAC, decode it, build the logon token. 193 */ 194 195 if (gname != NULL && GSS_S_COMPLETE == 196 gss_display_name(&minor, gname, &namebuf, &name_type)) { 197 /* Save the user name. */ 198 be->be_username = strdup(namebuf.value); 199 (void) gss_release_buffer(&minor, &namebuf); 200 (void) gss_release_name(&minor, &gname); 201 if (be->be_username == NULL) { 202 return (NT_STATUS_NO_MEMORY); 203 } 204 } 205 206 /* 207 * Extract the KRB5_AUTHDATA_WIN2K_PAC data. 208 */ 209 status = get_authz_data_pac(be->be_gssctx, 210 &be->be_authz_pac); 211 if (status) 212 goto out; 213 214 kerr = krb5_pac_parse(be->be_kctx, be->be_authz_pac.value, 215 be->be_authz_pac.length, &be->be_kpac); 216 if (kerr) { 217 smbd_report("krb5ssp, krb5_pac_parse: %s", 218 krb5_get_error_message(be->be_kctx, kerr)); 219 status = NT_STATUS_UNSUCCESSFUL; 220 goto out; 221 } 222 223 kerr = krb5_pac_get_buffer(be->be_kctx, be->be_kpac, 224 PAC_LOGON_INFO, &be->be_pac); 225 if (kerr) { 226 smbd_report("krb5ssp, krb5_pac_get_buffer: %s", 227 krb5_get_error_message(be->be_kctx, kerr)); 228 status = NT_STATUS_UNSUCCESSFUL; 229 goto out; 230 } 231 232 ctx->ctx_token = calloc(1, sizeof (smb_token_t)); 233 if (ctx->ctx_token == NULL) { 234 status = NT_STATUS_NO_MEMORY; 235 goto out; 236 } 237 238 status = smb_decode_krb5_pac(ctx->ctx_token, be->be_pac.data, 239 be->be_pac.length); 240 if (status) 241 goto out; 242 243 status = get_ssnkey(ctx); 244 if (status) 245 goto out; 246 247 if (!smb_token_setup_common(ctx->ctx_token)) { 248 status = NT_STATUS_UNSUCCESSFUL; 249 goto out; 250 } 251 252 /* Success! */ 253 ctx->ctx_orawtype = LSA_MTYPE_ES_DONE; 254 255 status = 0; 256 token = ctx->ctx_token; 257 258 /* 259 * Before we return, audit successful and failed logons. 260 * 261 * Successful logons are audited using the username and domain 262 * contained in the ticket (where the domain comes from the PAC data). 263 * 264 * Failed logins use a 'default' domain. If we fail after obtaining 265 * the username in the ticket, we audit under that username. 266 * 267 * Prior to decoding the username, we only audit failures where we'll 268 * return NT_STATUS_WRONG_PASSWORD, so that we audit attempts with 269 * invalid (or forged) tickets. These records use a 'default' username; 270 * As such, they serve only to inform an administrator that 271 * a particular client used a bad ticket, but does not contain any 272 * information on the ticket itself. 273 * 274 * Once we have a username, we'll audit all failed authentications. 275 */ 276 out: 277 status = smbd_logon_final(token, &ctx->ctx_clinfo.lci_clnt_ipaddr, 278 (be->be_username != NULL) ? be->be_username : krb5ssp_def_username, 279 krb5ssp_def_domain, status); 280 281 return (status); 282 } 283 284 /* 285 * See: GSS_KRB5_EXTRACT_AUTHZ_DATA_FROM_SEC_CONTEXT_OID 286 * and: KRB5_AUTHDATA_WIN2K_PAC 287 */ 288 static const gss_OID_desc 289 oid_ex_authz_data_pac = { 290 13, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x0a\x81\x00" }; 291 292 /* 293 * See: krb5_gss_inquire_sec_context_by_oid() 294 * and krb5_gss_inquire_sec_context_by_oid_ops[], 295 * gss_krb5int_extract_authz_data_from_sec_context() 296 */ 297 static uint32_t 298 get_authz_data_pac( 299 gss_ctx_id_t context_handle, 300 gss_buffer_t ad_data) 301 { 302 gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET; 303 OM_uint32 major, minor; 304 uint32_t status = NT_STATUS_UNSUCCESSFUL; 305 306 if (ad_data == NULL) 307 goto out; 308 309 major = gss_inquire_sec_context_by_oid( 310 &minor, 311 context_handle, 312 (gss_OID)&oid_ex_authz_data_pac, 313 &data_set); 314 if (GSS_ERROR(major)) { 315 smbd_report("krb5ssp, gss_inquire...PAC, " 316 "major=0x%x, minor=0x%x", major, minor); 317 goto out; 318 } 319 320 if ((data_set == GSS_C_NO_BUFFER_SET) || (data_set->count == 0)) { 321 goto out; 322 } 323 324 /* Only need the first element? */ 325 ad_data->length = data_set->elements[0].length; 326 ad_data->value = malloc(ad_data->length); 327 if (ad_data->value == NULL) { 328 status = NT_STATUS_NO_MEMORY; 329 goto out; 330 } 331 bcopy(data_set->elements[0].value, ad_data->value, ad_data->length); 332 status = 0; 333 334 out: 335 (void) gss_release_buffer_set(&minor, &data_set); 336 337 return (status); 338 } 339 340 /* 341 * Get the session key, and save it in the token. 342 * 343 * See: krb5_gss_inquire_sec_context_by_oid(), 344 * krb5_gss_inquire_sec_context_by_oid_ops[], and 345 * gss_krb5int_inq_session_key 346 */ 347 static uint32_t 348 get_ssnkey(authsvc_context_t *ctx) 349 { 350 krb5ssp_backend_t *be = ctx->ctx_backend; 351 gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET; 352 OM_uint32 major, minor; 353 size_t keylen; 354 uint32_t status = NT_STATUS_UNSUCCESSFUL; 355 356 major = gss_inquire_sec_context_by_oid(&minor, 357 be->be_gssctx, GSS_C_INQ_SSPI_SESSION_KEY, &data_set); 358 if (GSS_ERROR(major)) { 359 smbd_report("krb5ssp, failed to get session key, " 360 "major=0x%x, minor=0x%x", major, minor); 361 goto out; 362 } 363 364 /* 365 * The key is in the first element 366 */ 367 if (data_set == GSS_C_NO_BUFFER_SET || 368 data_set->count == 0 || 369 data_set->elements[0].length == 0 || 370 data_set->elements[0].value == NULL) { 371 smbd_report("krb5ssp: Session key is missing"); 372 goto out; 373 } 374 if ((keylen = data_set->elements[0].length) < SMBAUTH_HASH_SZ) { 375 smbd_report("krb5ssp: Session key too short (%d)", 376 data_set->elements[0].length); 377 goto out; 378 } 379 380 ctx->ctx_token->tkn_ssnkey.val = malloc(keylen); 381 if (ctx->ctx_token->tkn_ssnkey.val == NULL) { 382 status = NT_STATUS_NO_MEMORY; 383 goto out; 384 } 385 ctx->ctx_token->tkn_ssnkey.len = keylen; 386 bcopy(data_set->elements[0].value, 387 ctx->ctx_token->tkn_ssnkey.val, keylen); 388 status = 0; 389 390 out: 391 (void) gss_release_buffer_set(&minor, &data_set); 392 return (status); 393 } 394