/* * auth_sspi.c * * RPCSEC_GSS client routines (using the Windows SSPI rather than GSS-API). * * Copyright (c) 2000 Dug Song . * All rights reserved, all wrongs reversed. * * COPYRIGHT (c) 2010 * The Regents of the University of Michigan * ALL RIGHTS RESERVED * * Permission is granted to use, copy, create derivative works * and redistribute this software and such derivative works * for any purpose, so long as the name of The University of * Michigan is not used in any advertising or publicity * pertaining to the use of distribution of this software * without specific, written prior authorization. If the * above copyright notice or any other identification of the * University of Michigan is included in any copy of any * portion of this software, then the disclaimer below must * also be included. * * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF * SUCH DAMAGES. * */ #include #include #include //#include #include #include #include #include #include #include #include //#include //#include static void authgss_nextverf(); static bool_t authgss_marshal(); static bool_t authgss_refresh(); static bool_t authgss_validate(); static void authgss_destroy(); static void authgss_destroy_context(); static bool_t authgss_wrap(); static bool_t authgss_unwrap(); /* * from mit-krb5-1.2.1 mechglue/mglueP.h: * Array of context IDs typed by mechanism OID */ typedef struct gss_union_ctx_id_t { gss_OID mech_type; gss_ctx_id_t internal_ctx_id; } gss_union_ctx_id_desc, *gss_union_ctx_id_t; static struct auth_ops authgss_ops = { authgss_nextverf, authgss_marshal, authgss_validate, authgss_refresh, authgss_destroy, authgss_wrap, authgss_unwrap }; #ifdef DEBUG /* useful as i add more mechanisms */ void print_rpc_gss_sec(struct rpc_gss_sec *ptr) { int i; char *p; log_debug("rpc_gss_sec:"); if(ptr->mech == NULL) log_debug("NULL gss_OID mech"); else { fprintf(stderr, " mechanism_OID: {"); p = (char *)ptr->mech->elements; for (i=0; i < ptr->mech->length; i++) /* First byte of OIDs encoded to save a byte */ if (i == 0) { int first, second; if (*p < 40) { first = 0; second = *p; } else if (40 <= *p && *p < 80) { first = 1; second = *p - 40; } else if (80 <= *p && *p < 127) { first = 2; second = *p - 80; } else { /* Invalid value! */ first = -1; second = -1; } fprintf(stderr, " %u %u", first, second); p++; } else { fprintf(stderr, " %u", (unsigned char)*p++); } fprintf(stderr, " }\n"); } fprintf(stderr, " qop: %d\n", ptr->qop); fprintf(stderr, " service: %d\n", ptr->svc); fprintf(stderr, " cred: %p\n", ptr->cred); } #endif /*DEBUG*/ struct rpc_gss_data { bool_t established; /* context established */ gss_buffer_desc gc_wire_verf; /* save GSS_S_COMPLETE NULL RPC verfier * to process at end of context negotiation*/ CLIENT *clnt; /* client handle */ gss_name_t name; /* service name */ struct rpc_gss_sec sec; /* security tuple */ gss_ctx_id_t ctx; /* context id */ struct rpc_gss_cred gc; /* client credentials */ u_int win; /* sequence window */ }; #define AUTH_PRIVATE(auth) ((struct rpc_gss_data *)auth->ah_private) static struct timeval AUTH_TIMEOUT = { 25, 0 }; AUTH * authgss_create(CLIENT *clnt, gss_name_t name, struct rpc_gss_sec *sec) { AUTH *auth, *save_auth; struct rpc_gss_data *gd; OM_uint32 min_stat = 0; log_debug("in authgss_create()"); memset(&rpc_createerr, 0, sizeof(rpc_createerr)); if ((auth = calloc(sizeof(*auth), 1)) == NULL) { rpc_createerr.cf_stat = RPC_SYSTEMERROR; rpc_createerr.cf_error.re_errno = ENOMEM; return (NULL); } if ((gd = calloc(sizeof(*gd), 1)) == NULL) { rpc_createerr.cf_stat = RPC_SYSTEMERROR; rpc_createerr.cf_error.re_errno = ENOMEM; free(auth); return (NULL); } #ifdef DEBUG fprintf(stderr, "authgss_create: name is %p\n", name); #endif if (name != GSS_C_NO_NAME) { if (gss_duplicate_name(&min_stat, name, &gd->name) != GSS_S_COMPLETE) { rpc_createerr.cf_stat = RPC_SYSTEMERROR; rpc_createerr.cf_error.re_errno = ENOMEM; free(auth); return (NULL); } } else gd->name = name; #ifdef DEBUG fprintf(stderr, "authgss_create: gd->name is %p\n", gd->name); #endif gd->clnt = clnt; gd->ctx = GSS_C_NO_CONTEXT; gd->sec = *sec; gd->gc.gc_v = RPCSEC_GSS_VERSION; gd->gc.gc_proc = RPCSEC_GSS_INIT; gd->gc.gc_svc = gd->sec.svc; auth->ah_ops = &authgss_ops; auth->ah_private = (caddr_t)gd; save_auth = clnt->cl_auth; clnt->cl_auth = auth; if (!authgss_refresh(auth)) auth = NULL; clnt->cl_auth = save_auth; return (auth); } AUTH * authgss_create_default(CLIENT *clnt, char *service, struct rpc_gss_sec *sec) { AUTH *auth; OM_uint32 maj_stat = 0, min_stat = 0; gss_buffer_desc sname; gss_name_t name = GSS_C_NO_NAME; log_debug("in authgss_create_default()"); sname.value = service; sname.length = strlen(service); maj_stat = gss_import_name(&min_stat, &sname, (gss_OID)GSS_C_NT_HOSTBASED_SERVICE, &name); if (maj_stat != GSS_S_COMPLETE) { log_status("gss_import_name", maj_stat, min_stat); rpc_createerr.cf_stat = RPC_AUTHERROR; return (NULL); } auth = authgss_create(clnt, name, sec); if (name != GSS_C_NO_NAME) { #ifdef DEBUG fprintf(stderr, "authgss_create_default: freeing name %p\n", name); #endif gss_release_name(&min_stat, &name); } return (auth); } bool_t authgss_get_private_data(AUTH *auth, struct authgss_private_data *pd) { struct rpc_gss_data *gd; log_debug("in authgss_get_private_data()"); if (!auth || !pd) return (FALSE); gd = AUTH_PRIVATE(auth); if (!gd || !gd->established) return (FALSE); pd->pd_ctx = gd->ctx; pd->pd_ctx_hndl = gd->gc.gc_ctx; pd->pd_seq_win = gd->win; return (TRUE); } static void authgss_nextverf(AUTH *auth) { log_debug("in authgss_nextverf()"); /* no action necessary */ } static bool_t authgss_marshal(AUTH *auth, XDR *xdrs) { XDR tmpxdrs; char tmp[MAX_AUTH_BYTES]; struct rpc_gss_data *gd; gss_buffer_desc rpcbuf, checksum; OM_uint32 maj_stat, min_stat; bool_t xdr_stat; log_debug("in authgss_marshal()"); gd = AUTH_PRIVATE(auth); if (gd->established) gd->gc.gc_seq++; xdrmem_create(&tmpxdrs, tmp, sizeof(tmp), XDR_ENCODE); if (!xdr_rpc_gss_cred(&tmpxdrs, &gd->gc)) { XDR_DESTROY(&tmpxdrs); return (FALSE); } auth->ah_cred.oa_flavor = RPCSEC_GSS; auth->ah_cred.oa_base = tmp; auth->ah_cred.oa_length = XDR_GETPOS(&tmpxdrs); XDR_DESTROY(&tmpxdrs); if (!xdr_opaque_auth(xdrs, &auth->ah_cred)) return (FALSE); if (gd->gc.gc_proc == RPCSEC_GSS_INIT || gd->gc.gc_proc == RPCSEC_GSS_CONTINUE_INIT) { return (xdr_opaque_auth(xdrs, &_null_auth)); } /* Checksum serialized RPC header, up to and including credential. */ rpcbuf.length = XDR_GETPOS(xdrs); XDR_SETPOS(xdrs, 0); rpcbuf.value = XDR_INLINE(xdrs, rpcbuf.length); maj_stat = gss_get_mic(&min_stat, gd->ctx, gd->sec.qop, &rpcbuf, &checksum); if (maj_stat != GSS_S_COMPLETE) { log_status("gss_get_mic", maj_stat, min_stat); if (maj_stat == GSS_S_CONTEXT_EXPIRED) { gd->established = FALSE; authgss_destroy_context(auth); } return (FALSE); } auth->ah_verf.oa_flavor = RPCSEC_GSS; auth->ah_verf.oa_base = checksum.value; auth->ah_verf.oa_length = checksum.length; xdr_stat = xdr_opaque_auth(xdrs, &auth->ah_verf); gss_release_buffer(&min_stat, &checksum); return (xdr_stat); } static bool_t authgss_validate(AUTH *auth, struct opaque_auth *verf) { struct rpc_gss_data *gd; u_int num, qop_state; gss_buffer_desc signbuf, checksum; OM_uint32 maj_stat, min_stat; log_debug("in authgss_validate()"); gd = AUTH_PRIVATE(auth); if (gd->established == FALSE) { /* would like to do this only on NULL rpc -- * gc->established is good enough. * save the on the wire verifier to validate last * INIT phase packet after decode if the major * status is GSS_S_COMPLETE */ if ((gd->gc_wire_verf.value = mem_alloc(verf->oa_length)) == NULL) { fprintf(stderr, "gss_validate: out of memory\n"); return (FALSE); } memcpy(gd->gc_wire_verf.value, verf->oa_base, verf->oa_length); gd->gc_wire_verf.length = verf->oa_length; return (TRUE); } if (gd->gc.gc_proc == RPCSEC_GSS_INIT || gd->gc.gc_proc == RPCSEC_GSS_CONTINUE_INIT) { num = htonl(gd->win); } else num = htonl(gd->gc.gc_seq); signbuf.value = # signbuf.length = sizeof(num); checksum.value = verf->oa_base; checksum.length = verf->oa_length; maj_stat = gss_verify_mic(&min_stat, gd->ctx, &signbuf, &checksum, &qop_state); if (maj_stat != GSS_S_COMPLETE || qop_state != gd->sec.qop) { log_status("gss_verify_mic", maj_stat, min_stat); if (maj_stat == GSS_S_CONTEXT_EXPIRED) { gd->established = FALSE; authgss_destroy_context(auth); } return (FALSE); } return (TRUE); } static bool_t authgss_refresh(AUTH *auth) { struct rpc_gss_data *gd; struct rpc_gss_init_res gr; gss_buffer_desc *recv_tokenp, send_token; OM_uint32 maj_stat, min_stat, call_stat, ret_flags; log_debug("in authgss_refresh()"); gd = AUTH_PRIVATE(auth); if (gd->established) return (TRUE); /* GSS context establishment loop. */ memset(&gr, 0, sizeof(gr)); recv_tokenp = GSS_C_NO_BUFFER; #ifdef DEBUG print_rpc_gss_sec(&gd->sec); #endif /*DEBUG*/ for (;;) { #ifdef DEBUG /* print the token we just received */ if (recv_tokenp != GSS_C_NO_BUFFER) { log_debug("The token we just received (length %d):", recv_tokenp->length); log_hexdump(recv_tokenp->value, recv_tokenp->length, 0); } #endif maj_stat = gss_init_sec_context(&min_stat, gd->sec.cred, &gd->ctx, gd->name, gd->sec.mech, gd->sec.req_flags, 0, /* time req */ NULL, /* channel */ recv_tokenp, NULL, /* used mech */ &send_token, &ret_flags, NULL); /* time rec */ if (recv_tokenp != GSS_C_NO_BUFFER) { gss_release_buffer(&min_stat, &gr.gr_token); recv_tokenp = GSS_C_NO_BUFFER; } if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) { log_status("gss_init_sec_context", maj_stat, min_stat); break; } if (send_token.length != 0) { memset(&gr, 0, sizeof(gr)); #ifdef DEBUG /* print the token we are about to send */ log_debug("The token being sent (length %d):", send_token.length); log_hexdump(send_token.value, send_token.length, 0); #endif call_stat = clnt_call(gd->clnt, NULLPROC, (xdrproc_t)xdr_rpc_gss_init_args, &send_token, (xdrproc_t)xdr_rpc_gss_init_res, (caddr_t)&gr, AUTH_TIMEOUT); gss_release_buffer(&min_stat, &send_token); if (call_stat != RPC_SUCCESS || (gr.gr_major != GSS_S_COMPLETE && gr.gr_major != GSS_S_CONTINUE_NEEDED)) return FALSE; if (gr.gr_ctx.length != 0) { if (gd->gc.gc_ctx.value) gss_release_buffer(&min_stat, &gd->gc.gc_ctx); gd->gc.gc_ctx = gr.gr_ctx; } if (gr.gr_token.length != 0) { if (maj_stat != GSS_S_CONTINUE_NEEDED) break; recv_tokenp = &gr.gr_token; } gd->gc.gc_proc = RPCSEC_GSS_CONTINUE_INIT; } /* GSS_S_COMPLETE => check gss header verifier, * usually checked in gss_validate */ if (maj_stat == GSS_S_COMPLETE) { gss_buffer_desc bufin; gss_buffer_desc bufout; u_int seq, qop_state = 0; seq = htonl(gr.gr_win); bufin.value = (unsigned char *)&seq; bufin.length = sizeof(seq); bufout.value = (unsigned char *)gd->gc_wire_verf.value; bufout.length = gd->gc_wire_verf.length; maj_stat = gss_verify_mic(&min_stat, gd->ctx, &bufin, &bufout, &qop_state); if (maj_stat != GSS_S_COMPLETE || qop_state != gd->sec.qop) { log_status("gss_verify_mic", maj_stat, min_stat); if (maj_stat == GSS_S_CONTEXT_EXPIRED) { gd->established = FALSE; authgss_destroy_context(auth); } return (FALSE); } gd->established = TRUE; gd->gc.gc_proc = RPCSEC_GSS_DATA; gd->gc.gc_seq = 0; gd->win = gr.gr_win; break; } } /* End context negotiation loop. */ if (gd->gc.gc_proc != RPCSEC_GSS_DATA) { if (gr.gr_token.length != 0) gss_release_buffer(&min_stat, &gr.gr_token); authgss_destroy(auth); auth = NULL; rpc_createerr.cf_stat = RPC_AUTHERROR; return (FALSE); } return (TRUE); } bool_t authgss_service(AUTH *auth, int svc) { struct rpc_gss_data *gd; log_debug("in authgss_service()"); if (!auth) return(FALSE); gd = AUTH_PRIVATE(auth); if (!gd || !gd->established) return (FALSE); gd->sec.svc = svc; gd->gc.gc_svc = svc; return (TRUE); } static void authgss_destroy_context(AUTH *auth) { struct rpc_gss_data *gd; OM_uint32 min_stat; log_debug("in authgss_destroy_context()"); gd = AUTH_PRIVATE(auth); if (gd->gc.gc_ctx.length != 0) { if (gd->established) { gd->gc.gc_proc = RPCSEC_GSS_DESTROY; clnt_call(gd->clnt, NULLPROC, (xdrproc_t)xdr_void, NULL, (xdrproc_t)xdr_void, NULL, AUTH_TIMEOUT); } gss_release_buffer(&min_stat, &gd->gc.gc_ctx); /* XXX ANDROS check size of context - should be 8 */ memset(&gd->gc.gc_ctx, 0, sizeof(gd->gc.gc_ctx)); } if (gd->ctx != GSS_C_NO_CONTEXT) { gss_delete_sec_context(&min_stat, &gd->ctx, NULL); gd->ctx = GSS_C_NO_CONTEXT; } /* free saved wire verifier (if any) */ mem_free(gd->gc_wire_verf.value, gd->gc_wire_verf.length); gd->gc_wire_verf.value = NULL; gd->gc_wire_verf.length = 0; gd->established = FALSE; } static void authgss_destroy(AUTH *auth) { struct rpc_gss_data *gd; OM_uint32 min_stat; log_debug("in authgss_destroy()"); gd = AUTH_PRIVATE(auth); authgss_destroy_context(auth); #ifdef DEBUG fprintf(stderr, "authgss_destroy: freeing name %p\n", gd->name); #endif if (gd->name != GSS_C_NO_NAME) gss_release_name(&min_stat, &gd->name); free(gd); free(auth); } bool_t authgss_wrap(AUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr) { struct rpc_gss_data *gd; log_debug("in authgss_wrap()"); gd = AUTH_PRIVATE(auth); if (!gd->established || gd->sec.svc == RPCSEC_GSS_SVC_NONE) { return ((*xdr_func)(xdrs, xdr_ptr)); } return (xdr_rpc_gss_data(xdrs, xdr_func, xdr_ptr, gd->ctx, gd->sec.qop, gd->sec.svc, gd->gc.gc_seq)); } bool_t authgss_unwrap(AUTH *auth, XDR *xdrs, xdrproc_t xdr_func, caddr_t xdr_ptr) { struct rpc_gss_data *gd; log_debug("in authgss_unwrap()"); gd = AUTH_PRIVATE(auth); if (!gd->established || gd->sec.svc == RPCSEC_GSS_SVC_NONE) { return ((*xdr_func)(xdrs, xdr_ptr)); } return (xdr_rpc_gss_data(xdrs, xdr_func, xdr_ptr, gd->ctx, gd->sec.qop, gd->sec.svc, gd->gc.gc_seq)); }