2 * Copyright (c) 2000, Boris Popov
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by Boris Popov.
16 * 4. Neither the name of the author nor the names of any co-contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
38 * Kerberos V Security Support Provider
40 * Based on code previously in ctx.c (from Boris Popov?)
41 * but then mostly rewritten at Sun.
55 #include <sys/types.h>
57 #include <sys/byteorder.h>
58 #include <sys/socket.h>
59 #include <sys/fcntl.h>
61 #include <netinet/in.h>
62 #include <netinet/tcp.h>
63 #include <arpa/inet.h>
65 #include <netsmb/smb.h>
66 #include <netsmb/smb_lib.h>
67 #include <netsmb/mchain.h>
75 #include <kerberosv5/krb5.h>
76 #include <kerberosv5/com_err.h>
77 #include <gssapi/gssapi.h>
78 #include <gssapi/mechs/krb5/include/auth_con.h>
80 /* RFC 4121 checksum type ID. */
81 #define CKSUM_TYPE_RFC4121 0x8003
83 /* RFC 1964 token ID codes */
88 extern MECH_OID g_stcMechOIDList
[];
90 typedef struct krb5ssp_state
{
91 /* Filled in by krb5ssp_init_client */
92 krb5_context ss_krb5ctx
; /* krb5 context (ptr) */
93 krb5_ccache ss_krb5cc
; /* credentials cache (ptr) */
94 krb5_principal ss_krb5clp
; /* client principal (ptr) */
95 /* Filled in by krb5ssp_get_tkt */
96 krb5_auth_context ss_auth
; /* auth ctx. w/ server (ptr) */
101 * adds a GSSAPI wrapper
104 krb5ssp_tkt2gtok(uchar_t
*tkt
, ulong_t tktlen
,
105 uchar_t
**gtokp
, ulong_t
*gtoklenp
)
108 ulong_t bloblen
= tktlen
;
109 uchar_t krbapreq
[2] = { KRB_AP_REQ
, 0 };
110 uchar_t
*blob
= NULL
; /* result */
113 bloblen
+= sizeof (krbapreq
);
114 bloblen
+= g_stcMechOIDList
[spnego_mech_oid_Kerberos_V5
].iLen
;
116 bloblen
= ASNDerCalcTokenLength(bloblen
, bloblen
);
117 if ((blob
= malloc(bloblen
)) == NULL
) {
123 b
+= ASNDerWriteToken(b
, SPNEGO_NEGINIT_APP_CONSTRUCT
, NULL
, len
);
124 b
+= ASNDerWriteOID(b
, spnego_mech_oid_Kerberos_V5
);
125 memcpy(b
, krbapreq
, sizeof (krbapreq
));
126 b
+= sizeof (krbapreq
);
128 assert(b
+ tktlen
== blob
+ bloblen
);
129 memcpy(b
, tkt
, tktlen
);
136 * See "Windows 2000 Kerberos Interoperability" paper by
137 * Christopher Nebergall. RC4 HMAC is the W2K default but
138 * Samba support lagged (not due to Samba itself, but due to OS'
139 * Kerberos implementations.)
141 * Only session enc type should matter, not ticket enc type,
142 * per Sam Hartman on krbdev.
144 * Preauthentication failure topics in krb-protocol may help here...
145 * try "John Brezak" and/or "Clifford Neuman" too.
147 static krb5_enctype kenctypes
[] = {
148 ENCTYPE_ARCFOUR_HMAC
, /* defined in krb5.h */
154 static const int rq_opts
=
155 AP_OPTS_USE_SUBKEY
| AP_OPTS_MUTUAL_REQUIRED
;
158 * Obtain a kerberos ticket for the host we're connecting to.
159 * (This does the KRB_TGS exchange.)
162 krb5ssp_get_tkt(krb5ssp_state_t
*ss
, char *server
,
163 uchar_t
**tktp
, ulong_t
*tktlenp
)
165 krb5_context kctx
= ss
->ss_krb5ctx
;
166 krb5_ccache kcc
= ss
->ss_krb5cc
;
167 krb5_data indata
= {0};
168 krb5_data outdata
= {0};
169 krb5_error_code kerr
= 0;
170 const char *fn
= NULL
;
173 /* Should have these from krb5ssp_init_client. */
174 if (kctx
== NULL
|| kcc
== NULL
) {
175 fn
= "null kctx or kcc";
180 kerr
= krb5_set_default_tgs_enctypes(kctx
, kenctypes
);
182 fn
= "krb5_set_default_tgs_enctypes";
186 /* Get ss_auth now so we can set req_chsumtype. */
187 kerr
= krb5_auth_con_init(kctx
, &ss
->ss_auth
);
189 fn
= "krb5_auth_con_init";
192 /* Missing krb5_auth_con_set_req_cksumtype(), so inline. */
193 ss
->ss_auth
->req_cksumtype
= CKSUM_TYPE_RFC4121
;
196 * Build an RFC 4121 "checksum" with NULL channel bindings,
197 * like make_gss_checksum(). Numbers here from the RFC.
200 if ((indata
.data
= calloc(1, indata
.length
)) == NULL
) {
202 fn
= "malloc checksum";
205 indata
.data
[0] = 16; /* length of "Bnd" field. */
206 indata
.data
[20] = GSS_C_MUTUAL_FLAG
| GSS_C_INTEG_FLAG
;
207 /* Done building the "checksum". */
209 kerr
= krb5_mk_req(kctx
, &ss
->ss_auth
, rq_opts
, "cifs", server
,
210 &indata
, kcc
, &outdata
);
215 if ((tkt
= malloc(outdata
.length
)) == NULL
) {
217 fn
= "malloc signing key";
220 memcpy(tkt
, outdata
.data
, outdata
.length
);
222 *tktlenp
= outdata
.length
;
229 DPRINT("%s err 0x%x: %s", fn
, kerr
, error_message(kerr
));
230 if (kerr
<= 0 || kerr
> ESTALE
)
235 krb5_free_data_contents(kctx
, &outdata
);
239 /* Free kctx in krb5ssp_destroy */
245 * Build an RFC 1964 KRB_AP_REQ message
246 * The caller puts on the SPNEGO wrapper.
249 krb5ssp_put_request(struct ssp_ctx
*sp
, struct mbdata
*out_mb
)
252 struct smb_ctx
*ctx
= sp
->smb_ctx
;
253 krb5ssp_state_t
*ss
= sp
->sp_private
;
256 uchar_t
*gtok
= NULL
; /* gssapi token */
257 ulong_t gtoklen
; /* gssapi token length */
258 char *prin
= ctx
->ct_srvname
;
260 if ((err
= krb5ssp_get_tkt(ss
, prin
, &tkt
, &tktlen
)) != 0)
262 if ((err
= krb5ssp_tkt2gtok(tkt
, tktlen
, >ok
, >oklen
)) != 0)
265 if ((err
= mb_init_sz(out_mb
, gtoklen
)) != 0)
267 if ((err
= mb_put_mem(out_mb
, gtok
, gtoklen
, MB_MSYSTEM
)) != 0)
270 if (ctx
->ct_vcflags
& SMBV_WILL_SIGN
)
271 ctx
->ct_hflags2
|= SMB_FLAGS2_SECURITY_SIGNATURE
;
281 * Unwrap a GSS-API encapsulated RFC 1964 reply message,
282 * i.e. type KRB_AP_REP or KRB_ERROR.
285 krb5ssp_get_reply(struct ssp_ctx
*sp
, struct mbdata
*in_mb
)
287 krb5ssp_state_t
*ss
= sp
->sp_private
;
288 mbuf_t
*m
= in_mb
->mb_top
;
291 long actual_len
, token_len
;
294 krb5_ap_rep_enc_part
*reply
= NULL
;
296 /* cheating: this mbuf is contiguous */
297 assert(m
->m_data
== in_mb
->mb_pos
);
298 data
= (uchar_t
*)m
->m_data
;
302 * Peel off the GSS-API wrapper. Looks like:
304 * OID(KRB5): 06 09 2a 86 48 86 f7 12 01 02 02
307 rc
= ASNDerCheckToken(data
, SPNEGO_NEGINIT_APP_CONSTRUCT
,
308 0, dlen
, &token_len
, &actual_len
);
309 if (rc
!= SPNEGO_E_SUCCESS
) {
310 DPRINT("no AppToken? rc=0x%x", rc
);
313 if (dlen
< actual_len
)
319 rc
= ASNDerCheckOID(data
, spnego_mech_oid_Kerberos_V5
,
321 if (rc
!= SPNEGO_E_SUCCESS
) {
322 DPRINT("no OID? rc=0x%x", rc
);
325 if (dlen
< actual_len
)
330 /* KRB_AP_REP or KRB_ERROR */
331 if (data
[0] != KRB_AP_REP
) {
332 DPRINT("KRB5 type: %d", data
[1]);
341 * Now what's left should be a krb5 reply
342 * NB: ap is NOT allocated, so don't free it.
345 ap
.data
= (char *)data
;
346 rc
= krb5_rd_rep(ss
->ss_krb5ctx
, ss
->ss_auth
, &ap
, &reply
);
348 DPRINT("krb5_rd_rep: err 0x%x (%s)",
349 rc
, error_message(rc
));
355 * Have the decoded reply. Save anything?
357 * NB: If this returns an error, we will get
358 * no more calls into this back-end module.
364 krb5_free_ap_rep_enc_part(ss
->ss_krb5ctx
, reply
);
366 DPRINT("ret %d", err
);
374 * Called after successful authentication.
375 * Setup the MAC key for signing.
378 krb5ssp_final(struct ssp_ctx
*sp
)
380 struct smb_ctx
*ctx
= sp
->smb_ctx
;
381 krb5ssp_state_t
*ss
= sp
->sp_private
;
382 krb5_keyblock
*ssn_key
= NULL
;
386 * Save the session key, used for SMB signing
387 * and possibly other consumers (RPC).
389 err
= krb5_auth_con_getlocalsubkey(
390 ss
->ss_krb5ctx
, ss
->ss_auth
, &ssn_key
);
392 DPRINT("_getlocalsubkey, err=0x%x (%s)",
393 err
, error_message(err
));
394 if (err
<= 0 || err
> ESTALE
)
398 memset(ctx
->ct_ssn_key
, 0, SMBIOC_HASH_SZ
);
399 if ((len
= ssn_key
->length
) > SMBIOC_HASH_SZ
)
400 len
= SMBIOC_HASH_SZ
;
401 memcpy(ctx
->ct_ssn_key
, ssn_key
->contents
, len
);
404 * Set the MAC key on the first successful auth.
406 if ((ctx
->ct_hflags2
& SMB_FLAGS2_SECURITY_SIGNATURE
) &&
407 (ctx
->ct_mackey
== NULL
)) {
408 ctx
->ct_mackeylen
= ssn_key
->length
;
409 ctx
->ct_mackey
= malloc(ctx
->ct_mackeylen
);
410 if (ctx
->ct_mackey
== NULL
) {
411 ctx
->ct_mackeylen
= 0;
415 memcpy(ctx
->ct_mackey
, ssn_key
->contents
,
418 * Apparently, the server used seq. no. zero
419 * for our previous message, so next is two.
421 ctx
->ct_mac_seqno
= 2;
427 krb5_free_keyblock(ss
->ss_krb5ctx
, ssn_key
);
435 * See ssp.c: ssp_ctx_next_token
438 krb5ssp_next_token(struct ssp_ctx
*sp
, struct mbdata
*in_mb
,
439 struct mbdata
*out_mb
)
444 * Note: in_mb == NULL on the first call.
447 err
= krb5ssp_get_reply(sp
, in_mb
);
453 err
= krb5ssp_put_request(sp
, out_mb
);
455 err
= krb5ssp_final(sp
);
459 DPRINT("ret: %d", err
);
464 * krb5ssp_ctx_destroy
466 * Destroy mechanism-specific data.
469 krb5ssp_destroy(struct ssp_ctx
*sp
)
477 sp
->sp_private
= NULL
;
479 if ((kctx
= ss
->ss_krb5ctx
) != NULL
) {
480 /* from krb5ssp_get_tkt */
482 (void) krb5_auth_con_free(kctx
, ss
->ss_auth
);
483 /* from krb5ssp_init_client */
485 krb5_free_principal(kctx
, ss
->ss_krb5clp
);
487 (void) krb5_cc_close(kctx
, ss
->ss_krb5cc
);
488 krb5_free_context(kctx
);
497 * Initialize a new Kerberos SSP client context.
499 * The user must already have a TGT in their credential cache,
500 * as shown by the "klist" command.
503 krb5ssp_init_client(struct ssp_ctx
*sp
)
506 krb5_error_code kerr
;
507 krb5_context kctx
= NULL
;
508 krb5_ccache kcc
= NULL
;
509 krb5_principal kprin
= NULL
;
511 if ((sp
->smb_ctx
->ct_authflags
& SMB_AT_KRB5
) == 0) {
512 DPRINT("KRB5 not in authflags");
516 ss
= calloc(1, sizeof (*ss
));
520 sp
->sp_nexttok
= krb5ssp_next_token
;
521 sp
->sp_destroy
= krb5ssp_destroy
;
524 kerr
= krb5_init_context(&kctx
);
526 DPRINT("krb5_init_context, kerr 0x%x", kerr
);
529 ss
->ss_krb5ctx
= kctx
;
531 /* non-default would instead use krb5_cc_resolve */
532 kerr
= krb5_cc_default(kctx
, &kcc
);
534 DPRINT("krb5_cc_default, kerr 0x%x", kerr
);
540 * Get the client principal (ticket),
541 * or discover that we don't have one.
543 kerr
= krb5_cc_get_principal(kctx
, kcc
, &kprin
);
545 DPRINT("krb5_cc_get_principal, kerr 0x%x", kerr
);
548 ss
->ss_krb5clp
= kprin
;
551 DPRINT("Ticket cache: %s:%s",
552 krb5_cc_get_type(kctx
, kcc
),
553 krb5_cc_get_name(kctx
, kcc
));