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
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.
13 * Copyright 2015 Nexenta Systems, Inc. All rights reserved.
17 * SPNEGO back-end for Kerberos. See [MS-KILE]
20 #include <sys/types.h>
21 #include <gssapi/gssapi_ext.h>
22 #include <gssapi/gssapi_krb5.h>
25 #include "smbd_authsvc.h"
27 /* From krb5/krb/pac.c (should have been exported) */
28 #define PAC_LOGON_INFO 1
30 typedef struct krb5ssp_backend
{
31 gss_ctx_id_t be_gssctx
;
33 gss_buffer_desc be_authz_pac
;
41 gss_ctx_id_t context_handle
,
42 gss_buffer_t ad_data
);
45 get_ssnkey(authsvc_context_t
*ctx
);
49 * Initialize this context for Kerberos, if possible.
51 * Should not get here unless libsmb smb_config_get_negtok
52 * includes the Kerberos5 Mech OIDs in our spnego hint.
54 * Todo: allocate ctx->ctx_backend
55 * See: krb5_gss_accept_sec_context()
58 smbd_krb5ssp_init(authsvc_context_t
*ctx
)
60 krb5ssp_backend_t
*be
;
62 be
= malloc(sizeof (*be
));
64 return (NT_STATUS_NO_MEMORY
);
65 bzero(be
, sizeof (*be
));
66 be
->be_gssctx
= GSS_C_NO_CONTEXT
;
67 ctx
->ctx_backend
= be
;
73 * Todo: free ctx->ctx_backend
76 smbd_krb5ssp_fini(authsvc_context_t
*ctx
)
78 krb5ssp_backend_t
*be
= ctx
->ctx_backend
;
84 if (be
->be_kctx
!= NULL
) {
85 krb5_free_data_contents(be
->be_kctx
, &be
->be_pac
);
87 if (be
->be_kpac
!= NULL
)
88 krb5_pac_free(be
->be_kctx
, be
->be_kpac
);
90 krb5_free_context(be
->be_kctx
);
93 (void) gss_release_buffer(NULL
, &be
->be_authz_pac
);
95 free(be
->be_username
);
97 if (be
->be_gssctx
!= GSS_C_NO_CONTEXT
) {
98 (void) gss_delete_sec_context(&minor
, &be
->be_gssctx
,
106 * Handle a Kerberos auth message.
108 * State across messages is in ctx->ctx_backend
111 smbd_krb5ssp_work(authsvc_context_t
*ctx
)
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
;
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
));
128 /* Do this early, for error message support. */
129 kerr
= krb5_init_context(&be
->be_kctx
);
131 smbd_report("krb5ssp, krb5_init_ctx: %s",
132 krb5_get_error_message(be
->be_kctx
, kerr
));
133 return (NT_STATUS_INTERNAL_ERROR
);
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
);
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
);
149 free(ctx
->ctx_obodybuf
);
150 ctx
->ctx_obodybuf
= outtok
.value
;
151 ctx
->ctx_obodylen
= outtok
.length
;
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
);
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 */
173 return (NT_STATUS_WRONG_PASSWORD
);
175 return (NT_STATUS_WRONG_PASSWORD
);
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.
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
);
196 * Extract the KRB5_AUTHDATA_WIN2K_PAC data.
198 status
= get_authz_data_pac(be
->be_gssctx
,
203 kerr
= krb5_pac_parse(be
->be_kctx
, be
->be_authz_pac
.value
,
204 be
->be_authz_pac
.length
, &be
->be_kpac
);
206 smbd_report("krb5ssp, krb5_pac_parse: %s",
207 krb5_get_error_message(be
->be_kctx
, kerr
));
208 return (NT_STATUS_UNSUCCESSFUL
);
211 kerr
= krb5_pac_get_buffer(be
->be_kctx
, be
->be_kpac
,
212 PAC_LOGON_INFO
, &be
->be_pac
);
214 smbd_report("krb5ssp, krb5_pac_get_buffer: %s",
215 krb5_get_error_message(be
->be_kctx
, kerr
));
216 return (NT_STATUS_UNSUCCESSFUL
);
219 ctx
->ctx_token
= calloc(1, sizeof (smb_token_t
));
220 if (ctx
->ctx_token
== NULL
)
221 return (NT_STATUS_NO_MEMORY
);
223 status
= smb_decode_krb5_pac(ctx
->ctx_token
, be
->be_pac
.data
,
228 status
= get_ssnkey(ctx
);
232 if (!smb_token_setup_common(ctx
->ctx_token
))
233 return (NT_STATUS_UNSUCCESSFUL
);
236 ctx
->ctx_orawtype
= LSA_MTYPE_ES_DONE
;
242 * See: GSS_KRB5_EXTRACT_AUTHZ_DATA_FROM_SEC_CONTEXT_OID
243 * and: KRB5_AUTHDATA_WIN2K_PAC
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" };
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()
256 gss_ctx_id_t context_handle
,
257 gss_buffer_t ad_data
)
259 gss_buffer_set_t data_set
= GSS_C_NO_BUFFER_SET
;
260 OM_uint32 major
, minor
;
261 uint32_t status
= NT_STATUS_UNSUCCESSFUL
;
266 major
= gss_inquire_sec_context_by_oid(
269 (gss_OID
)&oid_ex_authz_data_pac
,
271 if (GSS_ERROR(major
)) {
272 smbd_report("krb5ssp, gss_inquire...PAC, "
273 "major=0x%x, minor=0x%x", major
, minor
);
277 if ((data_set
== GSS_C_NO_BUFFER_SET
) || (data_set
->count
== 0)) {
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
;
288 bcopy(data_set
->elements
[0].value
, ad_data
->value
, ad_data
->length
);
292 (void) gss_release_buffer_set(&minor
, &data_set
);
298 * Get the session key, and save it in the token.
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
305 get_ssnkey(authsvc_context_t
*ctx
)
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
;
311 uint32_t status
= NT_STATUS_UNSUCCESSFUL
;
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
);
322 * The key is in the first element
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");
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
);
337 ctx
->ctx_token
->tkn_ssnkey
.val
= malloc(keylen
);
338 if (ctx
->ctx_token
->tkn_ssnkey
.val
== NULL
) {
339 status
= NT_STATUS_NO_MEMORY
;
342 ctx
->ctx_token
->tkn_ssnkey
.len
= keylen
;
343 bcopy(data_set
->elements
[0].value
,
344 ctx
->ctx_token
->tkn_ssnkey
.val
, keylen
);
348 (void) gss_release_buffer_set(&minor
, &data_set
);