2 * Copyright (c) 2008 Doug Rabson
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.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 RPCSEC_GSS client routines.
33 Copyright (c) 2000 The Regents of the University of Michigan.
36 Copyright (c) 2000 Dug Song <dugsong@UMICH.EDU>.
37 All rights reserved, all wrongs reversed.
39 Redistribution and use in source and binary forms, with or without
40 modification, are permitted provided that the following conditions
43 1. Redistributions of source code must retain the above copyright
44 notice, this list of conditions and the following disclaimer.
45 2. Redistributions in binary form must reproduce the above copyright
46 notice, this list of conditions and the following disclaimer in the
47 documentation and/or other materials provided with the distribution.
48 3. Neither the name of the University nor the names of its
49 contributors may be used to endorse or promote products derived
50 from this software without specific prior written permission.
52 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
53 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
54 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
55 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
56 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
57 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
58 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
59 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
60 LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
61 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
62 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
64 $Id: auth_gss.c,v 1.32 2002/01/15 15:43:00 andros Exp $
72 #include <netinet/in.h>
74 #include <rpc/rpcsec_gss.h>
75 #include "rpcsec_gss_int.h"
77 static void rpc_gss_nextverf(AUTH*);
78 static bool_t rpc_gss_marshal(AUTH *, XDR *);
79 static bool_t rpc_gss_init(AUTH *auth, rpc_gss_options_ret_t *options_ret);
80 static bool_t rpc_gss_refresh(AUTH *, void *);
81 static bool_t rpc_gss_validate(AUTH *, struct opaque_auth *);
82 static void rpc_gss_destroy(AUTH *);
83 static void rpc_gss_destroy_context(AUTH *, bool_t);
85 static struct auth_ops rpc_gss_ops = {
93 enum rpcsec_gss_state {
96 RPCSEC_GSS_ESTABLISHED
100 rpc_gss_options_req_t gd_options; /* GSS context options */
101 enum rpcsec_gss_state gd_state; /* connection state */
102 gss_buffer_desc gd_verf; /* save GSS_S_COMPLETE
103 * NULL RPC verfier to
105 * context negotiation */
106 CLIENT *gd_clnt; /* client handle */
107 gss_name_t gd_name; /* service name */
108 gss_OID gd_mech; /* mechanism to use */
109 gss_qop_t gd_qop; /* quality of protection */
110 gss_ctx_id_t gd_ctx; /* context id */
111 struct rpc_gss_cred gd_cred; /* client credentials */
112 u_int gd_win; /* sequence window */
115 #define AUTH_PRIVATE(auth) ((struct rpc_gss_data *)auth->ah_private)
117 static struct timeval AUTH_TIMEOUT = { 25, 0 };
120 rpc_gss_seccreate(CLIENT *clnt, const char *principal,
121 const char *mechanism, rpc_gss_service_t service, const char *qop,
122 rpc_gss_options_req_t *options_req, rpc_gss_options_ret_t *options_ret)
124 AUTH *auth, *save_auth;
125 rpc_gss_options_ret_t options;
128 struct rpc_gss_data *gd;
129 OM_uint32 maj_stat = 0, min_stat = 0;
130 gss_buffer_desc principal_desc;
133 * Bail out now if we don't know this mechanism.
135 if (!rpc_gss_mech_to_oid(mechanism, &oid))
139 if (!rpc_gss_qop_to_num(qop, mechanism, &qop_num))
142 qop_num = GSS_C_QOP_DEFAULT;
146 * If the caller doesn't want the options, point at local
147 * storage to simplify the code below.
150 options_ret = &options;
153 * Default service is integrity.
155 if (service == rpc_gss_svc_default)
156 service = rpc_gss_svc_integrity;
158 memset(options_ret, 0, sizeof(*options_ret));
160 log_debug("in rpc_gss_seccreate()");
162 memset(&rpc_createerr, 0, sizeof(rpc_createerr));
164 auth = mem_alloc(sizeof(*auth));
166 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
167 rpc_createerr.cf_error.re_errno = ENOMEM;
170 gd = mem_alloc(sizeof(*gd));
172 rpc_createerr.cf_stat = RPC_SYSTEMERROR;
173 rpc_createerr.cf_error.re_errno = ENOMEM;
178 auth->ah_ops = &rpc_gss_ops;
179 auth->ah_private = (caddr_t) gd;
180 auth->ah_cred.oa_flavor = RPCSEC_GSS;
182 principal_desc.value = (void *)(intptr_t) principal;
183 principal_desc.length = strlen(principal);
184 maj_stat = gss_import_name(&min_stat, &principal_desc,
185 GSS_C_NT_HOSTBASED_SERVICE, &gd->gd_name);
186 if (maj_stat != GSS_S_COMPLETE) {
187 options_ret->major_status = maj_stat;
188 options_ret->minor_status = min_stat;
193 gd->gd_options = *options_req;
195 gd->gd_options.req_flags = GSS_C_MUTUAL_FLAG;
196 gd->gd_options.time_req = 0;
197 gd->gd_options.my_cred = GSS_C_NO_CREDENTIAL;
198 gd->gd_options.input_channel_bindings = NULL;
201 gd->gd_ctx = GSS_C_NO_CONTEXT;
203 gd->gd_qop = qop_num;
205 gd->gd_cred.gc_version = RPCSEC_GSS_VERSION;
206 gd->gd_cred.gc_proc = RPCSEC_GSS_INIT;
207 gd->gd_cred.gc_seq = 0;
208 gd->gd_cred.gc_svc = service;
210 save_auth = clnt->cl_auth;
212 clnt->cl_auth = auth;
213 if (!rpc_gss_init(auth, options_ret)) {
214 clnt->cl_auth = save_auth;
218 clnt->cl_auth = save_auth;
228 rpc_gss_set_defaults(AUTH *auth, rpc_gss_service_t service, const char *qop)
230 struct rpc_gss_data *gd;
232 const char *mechanism;
234 gd = AUTH_PRIVATE(auth);
235 if (!rpc_gss_oid_to_mech(gd->gd_mech, &mechanism)) {
240 if (!rpc_gss_qop_to_num(qop, mechanism, &qop_num)) {
244 qop_num = GSS_C_QOP_DEFAULT;
247 gd->gd_cred.gc_svc = service;
248 gd->gd_qop = qop_num;
253 rpc_gss_nextverf(__unused AUTH *auth)
260 rpc_gss_marshal(__unused AUTH *auth, __unused XDR *xdrs)
268 rpc_gss_validate(AUTH *auth, struct opaque_auth *verf)
270 struct rpc_gss_data *gd;
273 gss_buffer_desc signbuf, checksum;
274 OM_uint32 maj_stat, min_stat;
276 log_debug("in rpc_gss_validate()");
278 gd = AUTH_PRIVATE(auth);
280 if (gd->gd_state == RPCSEC_GSS_CONTEXT) {
282 * Save the on the wire verifier to validate last INIT
283 * phase packet after decode if the major status is
286 if (gd->gd_verf.value)
287 xdr_free((xdrproc_t) xdr_gss_buffer_desc,
288 (char *) &gd->gd_verf);
289 gd->gd_verf.value = mem_alloc(verf->oa_length);
290 if (gd->gd_verf.value == NULL) {
291 fprintf(stderr, "gss_validate: out of memory\n");
292 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
295 memcpy(gd->gd_verf.value, verf->oa_base, verf->oa_length);
296 gd->gd_verf.length = verf->oa_length;
300 num = htonl(gd->gd_cred.gc_seq);
301 signbuf.value = #
302 signbuf.length = sizeof(num);
304 checksum.value = verf->oa_base;
305 checksum.length = verf->oa_length;
307 maj_stat = gss_verify_mic(&min_stat, gd->gd_ctx, &signbuf,
308 &checksum, &qop_state);
309 if (maj_stat != GSS_S_COMPLETE || qop_state != gd->gd_qop) {
310 log_status("gss_verify_mic", gd->gd_mech, maj_stat, min_stat);
311 if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
312 rpc_gss_destroy_context(auth, TRUE);
314 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, EPERM);
321 rpc_gss_init(AUTH *auth, rpc_gss_options_ret_t *options_ret)
323 struct rpc_gss_data *gd;
324 struct rpc_gss_init_res gr;
325 gss_buffer_desc *recv_tokenp, recv_token, send_token;
326 OM_uint32 maj_stat, min_stat, call_stat;
329 log_debug("in rpc_gss_refresh()");
331 gd = AUTH_PRIVATE(auth);
333 if (gd->gd_state != RPCSEC_GSS_START)
336 /* GSS context establishment loop. */
337 gd->gd_state = RPCSEC_GSS_CONTEXT;
338 gd->gd_cred.gc_proc = RPCSEC_GSS_INIT;
339 gd->gd_cred.gc_seq = 0;
341 memset(&recv_token, 0, sizeof(recv_token));
342 memset(&gr, 0, sizeof(gr));
343 recv_tokenp = GSS_C_NO_BUFFER;
346 maj_stat = gss_init_sec_context(&min_stat,
347 gd->gd_options.my_cred,
351 gd->gd_options.req_flags,
352 gd->gd_options.time_req,
353 gd->gd_options.input_channel_bindings,
355 &gd->gd_mech, /* used mech */
357 &options_ret->ret_flags,
358 &options_ret->time_req);
361 * Free the token which we got from the server (if
362 * any). Remember that this was allocated by XDR, not
365 if (recv_tokenp != GSS_C_NO_BUFFER) {
366 xdr_free((xdrproc_t) xdr_gss_buffer_desc,
367 (char *) &recv_token);
368 recv_tokenp = GSS_C_NO_BUFFER;
370 if (maj_stat != GSS_S_COMPLETE &&
371 maj_stat != GSS_S_CONTINUE_NEEDED) {
372 log_status("gss_init_sec_context", gd->gd_mech,
374 options_ret->major_status = maj_stat;
375 options_ret->minor_status = min_stat;
378 if (send_token.length != 0) {
379 memset(&gr, 0, sizeof(gr));
381 call_stat = clnt_call(gd->gd_clnt, NULLPROC,
382 (xdrproc_t)xdr_gss_buffer_desc,
384 (xdrproc_t)xdr_rpc_gss_init_res,
385 (caddr_t)&gr, AUTH_TIMEOUT);
387 gss_release_buffer(&min_stat, &send_token);
389 if (call_stat != RPC_SUCCESS)
392 if (gr.gr_major != GSS_S_COMPLETE &&
393 gr.gr_major != GSS_S_CONTINUE_NEEDED) {
394 log_status("server reply", gd->gd_mech,
395 gr.gr_major, gr.gr_minor);
396 options_ret->major_status = gr.gr_major;
397 options_ret->minor_status = gr.gr_minor;
402 * Save the server's gr_handle value, freeing
403 * what we have already (remember that this
404 * was allocated by XDR, not GSS-API).
406 if (gr.gr_handle.length != 0) {
407 xdr_free((xdrproc_t) xdr_gss_buffer_desc,
408 (char *) &gd->gd_cred.gc_handle);
409 gd->gd_cred.gc_handle = gr.gr_handle;
413 * Save the server's token as well.
415 if (gr.gr_token.length != 0) {
416 recv_token = gr.gr_token;
417 recv_tokenp = &recv_token;
421 * Since we have copied out all the bits of gr
422 * which XDR allocated for us, we don't need
425 gd->gd_cred.gc_proc = RPCSEC_GSS_CONTINUE_INIT;
428 if (maj_stat == GSS_S_COMPLETE) {
429 gss_buffer_desc bufin;
430 u_int seq, qop_state = 0;
433 * gss header verifier,
434 * usually checked in gss_validate
436 seq = htonl(gr.gr_win);
437 bufin.value = (unsigned char *)&seq;
438 bufin.length = sizeof(seq);
440 maj_stat = gss_verify_mic(&min_stat, gd->gd_ctx,
441 &bufin, &gd->gd_verf, &qop_state);
443 if (maj_stat != GSS_S_COMPLETE ||
444 qop_state != gd->gd_qop) {
445 log_status("gss_verify_mic", gd->gd_mech,
447 if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
448 rpc_gss_destroy_context(auth, TRUE);
450 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR,
452 options_ret->major_status = maj_stat;
453 options_ret->minor_status = min_stat;
457 options_ret->major_status = GSS_S_COMPLETE;
458 options_ret->minor_status = 0;
459 options_ret->rpcsec_version = gd->gd_cred.gc_version;
460 options_ret->gss_context = gd->gd_ctx;
461 if (rpc_gss_oid_to_mech(gd->gd_mech, &mech)) {
462 strlcpy(options_ret->actual_mechanism,
464 sizeof(options_ret->actual_mechanism));
467 gd->gd_state = RPCSEC_GSS_ESTABLISHED;
468 gd->gd_cred.gc_proc = RPCSEC_GSS_DATA;
469 gd->gd_cred.gc_seq = 0;
470 gd->gd_win = gr.gr_win;
474 xdr_free((xdrproc_t) xdr_gss_buffer_desc,
475 (char *) &gd->gd_verf);
477 /* End context negotiation loop. */
478 if (gd->gd_cred.gc_proc != RPCSEC_GSS_DATA) {
479 rpc_createerr.cf_stat = RPC_AUTHERROR;
480 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, EPERM);
488 rpc_gss_refresh(AUTH *auth, void *msg)
490 struct rpc_msg *reply = (struct rpc_msg *) msg;
491 rpc_gss_options_ret_t options;
494 * If the error was RPCSEC_GSS_CREDPROBLEM of
495 * RPCSEC_GSS_CTXPROBLEM we start again from scratch. All
496 * other errors are fatal.
498 if (reply->rm_reply.rp_stat == MSG_DENIED
499 && reply->rm_reply.rp_rjct.rj_stat == AUTH_ERROR
500 && (reply->rm_reply.rp_rjct.rj_why == RPCSEC_GSS_CREDPROBLEM
501 || reply->rm_reply.rp_rjct.rj_why == RPCSEC_GSS_CTXPROBLEM)) {
502 rpc_gss_destroy_context(auth, FALSE);
503 memset(&options, 0, sizeof(options));
504 return (rpc_gss_init(auth, &options));
511 rpc_gss_destroy_context(AUTH *auth, bool_t send_destroy)
513 struct rpc_gss_data *gd;
516 log_debug("in rpc_gss_destroy_context()");
518 gd = AUTH_PRIVATE(auth);
520 if (gd->gd_state == RPCSEC_GSS_ESTABLISHED && send_destroy) {
521 gd->gd_cred.gc_proc = RPCSEC_GSS_DESTROY;
522 clnt_call(gd->gd_clnt, NULLPROC,
523 (xdrproc_t)xdr_void, NULL,
524 (xdrproc_t)xdr_void, NULL, AUTH_TIMEOUT);
528 * Free the context token. Remember that this was
529 * allocated by XDR, not GSS-API.
531 xdr_free((xdrproc_t) xdr_gss_buffer_desc,
532 (char *) &gd->gd_cred.gc_handle);
533 gd->gd_cred.gc_handle.length = 0;
535 if (gd->gd_ctx != GSS_C_NO_CONTEXT)
536 gss_delete_sec_context(&min_stat, &gd->gd_ctx, NULL);
538 gd->gd_state = RPCSEC_GSS_START;
542 rpc_gss_destroy(AUTH *auth)
544 struct rpc_gss_data *gd;
547 log_debug("in rpc_gss_destroy()");
549 gd = AUTH_PRIVATE(auth);
551 rpc_gss_destroy_context(auth, TRUE);
553 if (gd->gd_name != GSS_C_NO_NAME)
554 gss_release_name(&min_stat, &gd->gd_name);
555 if (gd->gd_verf.value)
556 xdr_free((xdrproc_t) xdr_gss_buffer_desc,
557 (char *) &gd->gd_verf);
559 mem_free(gd, sizeof(*gd));
560 mem_free(auth, sizeof(*auth));
564 __rpc_gss_wrap(AUTH *auth, void *header, size_t headerlen,
565 XDR* xdrs, xdrproc_t xdr_args, void *args_ptr)
568 char credbuf[MAX_AUTH_BYTES];
569 char tmpheader[MAX_AUTH_BYTES];
570 struct opaque_auth creds, verf;
571 struct rpc_gss_data *gd;
572 gss_buffer_desc rpcbuf, checksum;
573 OM_uint32 maj_stat, min_stat;
576 log_debug("in rpc_gss_wrap()");
578 gd = AUTH_PRIVATE(auth);
580 if (gd->gd_state == RPCSEC_GSS_ESTABLISHED)
581 gd->gd_cred.gc_seq++;
584 * We need to encode our creds and then put the header and
585 * creds together in a buffer so that we can create a checksum
588 xdrmem_create(&tmpxdrs, credbuf, sizeof(credbuf), XDR_ENCODE);
589 if (!xdr_rpc_gss_cred(&tmpxdrs, &gd->gd_cred)) {
590 XDR_DESTROY(&tmpxdrs);
591 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
594 creds.oa_flavor = RPCSEC_GSS;
595 creds.oa_base = credbuf;
596 creds.oa_length = XDR_GETPOS(&tmpxdrs);
597 XDR_DESTROY(&tmpxdrs);
599 xdrmem_create(&tmpxdrs, tmpheader, sizeof(tmpheader), XDR_ENCODE);
600 if (!XDR_PUTBYTES(&tmpxdrs, header, headerlen) ||
601 !xdr_opaque_auth(&tmpxdrs, &creds)) {
602 XDR_DESTROY(&tmpxdrs);
603 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
606 headerlen = XDR_GETPOS(&tmpxdrs);
607 XDR_DESTROY(&tmpxdrs);
609 if (!XDR_PUTBYTES(xdrs, tmpheader, headerlen)) {
610 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
614 if (gd->gd_cred.gc_proc == RPCSEC_GSS_INIT ||
615 gd->gd_cred.gc_proc == RPCSEC_GSS_CONTINUE_INIT) {
616 if (!xdr_opaque_auth(xdrs, &_null_auth)) {
617 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
622 * Checksum serialized RPC header, up to and including
625 rpcbuf.length = headerlen;
626 rpcbuf.value = tmpheader;
628 maj_stat = gss_get_mic(&min_stat, gd->gd_ctx, gd->gd_qop,
631 if (maj_stat != GSS_S_COMPLETE) {
632 log_status("gss_get_mic", gd->gd_mech,
634 if (maj_stat == GSS_S_CONTEXT_EXPIRED) {
635 rpc_gss_destroy_context(auth, TRUE);
637 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, EPERM);
641 verf.oa_flavor = RPCSEC_GSS;
642 verf.oa_base = checksum.value;
643 verf.oa_length = checksum.length;
645 xdr_stat = xdr_opaque_auth(xdrs, &verf);
646 gss_release_buffer(&min_stat, &checksum);
648 _rpc_gss_set_error(RPC_GSS_ER_SYSTEMERROR, ENOMEM);
653 if (gd->gd_state != RPCSEC_GSS_ESTABLISHED ||
654 gd->gd_cred.gc_svc == rpc_gss_svc_none) {
655 return (xdr_args(xdrs, args_ptr));
657 return (xdr_rpc_gss_wrap_data(xdrs, xdr_args, args_ptr,
658 gd->gd_ctx, gd->gd_qop, gd->gd_cred.gc_svc,
659 gd->gd_cred.gc_seq));
663 __rpc_gss_unwrap(AUTH *auth, XDR *xdrs, xdrproc_t xdr_func, void *xdr_ptr)
665 struct rpc_gss_data *gd;
667 log_debug("in rpc_gss_unwrap()");
669 gd = AUTH_PRIVATE(auth);
671 if (gd->gd_state != RPCSEC_GSS_ESTABLISHED ||
672 gd->gd_cred.gc_svc == rpc_gss_svc_none) {
673 return (xdr_func(xdrs, xdr_ptr));
675 return (xdr_rpc_gss_unwrap_data(xdrs, xdr_func, xdr_ptr,
676 gd->gd_ctx, gd->gd_qop, gd->gd_cred.gc_svc,
677 gd->gd_cred.gc_seq));
681 rpc_gss_max_data_length(AUTH *auth, int max_tp_unit_len)
683 struct rpc_gss_data *gd;
686 OM_uint32 maj_stat, min_stat;
689 gd = AUTH_PRIVATE(auth);
691 switch (gd->gd_cred.gc_svc) {
692 case rpc_gss_svc_none:
693 return (max_tp_unit_len);
696 case rpc_gss_svc_default:
697 case rpc_gss_svc_integrity:
701 case rpc_gss_svc_privacy:
709 maj_stat = gss_wrap_size_limit(&min_stat, gd->gd_ctx, want_conf,
710 gd->gd_qop, max_tp_unit_len, &max);
712 if (maj_stat == GSS_S_COMPLETE) {
718 log_status("gss_wrap_size_limit", gd->gd_mech,