diff options
Diffstat (limited to 'src/lib/krb5/krb')
27 files changed, 2152 insertions, 281 deletions
diff --git a/src/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in index 1a204dfd27..359a5e8d2f 100644 --- a/src/lib/krb5/krb/Makefile.in +++ b/src/lib/krb5/krb/Makefile.in @@ -64,6 +64,7 @@ STLIBOBJS= \ mk_req.o \ mk_req_ext.o \ mk_safe.o \ + pac.o \ parse.o \ pr_to_salt.o \ preauth.o \ @@ -150,7 +151,8 @@ OBJS= $(OUTPRE)addr_comp.$(OBJEXT) \ $(OUTPRE)mk_req.$(OBJEXT) \ $(OUTPRE)mk_req_ext.$(OBJEXT) \ $(OUTPRE)mk_safe.$(OBJEXT) \ - $(OUTPRE)parse.$(OBJEXT) \ + $(OUTPRE)pac.$(OBJEXT) \ + $(OUTPRE)parse.$(OBJEXT) \ $(OUTPRE)pr_to_salt.$(OBJEXT) \ $(OUTPRE)preauth.$(OBJEXT) \ $(OUTPRE)preauth2.$(OBJEXT) \ @@ -237,6 +239,7 @@ SRCS= $(srcdir)/addr_comp.c \ $(srcdir)/mk_req.c \ $(srcdir)/mk_req_ext.c \ $(srcdir)/mk_safe.c \ + $(srcdir)/pac.c \ $(srcdir)/parse.c \ $(srcdir)/pr_to_salt.c \ $(srcdir)/preauth.c \ @@ -709,7 +712,7 @@ get_creds.so get_creds.po $(OUTPRE)get_creds.$(OBJEXT): \ $(SRCTOP)/include/k5-plugin.h $(SRCTOP)/include/k5-thread.h \ $(SRCTOP)/include/krb5.h $(SRCTOP)/include/krb5/locate_plugin.h \ $(SRCTOP)/include/krb5/preauth_plugin.h $(SRCTOP)/include/port-sockets.h \ - $(SRCTOP)/include/socket-utils.h get_creds.c + $(SRCTOP)/include/socket-utils.h get_creds.c int-proto.h get_in_tkt.so get_in_tkt.po $(OUTPRE)get_in_tkt.$(OBJEXT): \ $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ @@ -882,6 +885,16 @@ mk_safe.so mk_safe.po $(OUTPRE)mk_safe.$(OBJEXT): $(BUILDTOP)/include/autoconf.h $(SRCTOP)/include/krb5/locate_plugin.h $(SRCTOP)/include/krb5/preauth_plugin.h \ $(SRCTOP)/include/port-sockets.h $(SRCTOP)/include/socket-utils.h \ auth_con.h cleanup.h mk_safe.c +pac.so pac.po $(OUTPRE)pac.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ + $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ + $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(SRCTOP)/include/k5-buf.h \ + $(SRCTOP)/include/k5-err.h $(SRCTOP)/include/k5-gmt_mktime.h \ + $(SRCTOP)/include/k5-int-pkinit.h $(SRCTOP)/include/k5-int.h \ + $(SRCTOP)/include/k5-platform.h $(SRCTOP)/include/k5-plugin.h \ + $(SRCTOP)/include/k5-thread.h $(SRCTOP)/include/k5-utf8.h \ + $(SRCTOP)/include/krb5.h $(SRCTOP)/include/krb5/locate_plugin.h \ + $(SRCTOP)/include/krb5/preauth_plugin.h $(SRCTOP)/include/port-sockets.h \ + $(SRCTOP)/include/socket-utils.h pac.c parse.so parse.po $(OUTPRE)parse.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(SRCTOP)/include/k5-buf.h \ @@ -929,6 +942,7 @@ princ_comp.so princ_comp.po $(OUTPRE)princ_comp.$(OBJEXT): \ $(SRCTOP)/include/k5-gmt_mktime.h $(SRCTOP)/include/k5-int-pkinit.h \ $(SRCTOP)/include/k5-int.h $(SRCTOP)/include/k5-platform.h \ $(SRCTOP)/include/k5-plugin.h $(SRCTOP)/include/k5-thread.h \ + $(SRCTOP)/include/k5-unicode.h $(SRCTOP)/include/k5-utf8.h \ $(SRCTOP)/include/krb5.h $(SRCTOP)/include/krb5/locate_plugin.h \ $(SRCTOP)/include/krb5/preauth_plugin.h $(SRCTOP)/include/port-sockets.h \ $(SRCTOP)/include/socket-utils.h princ_comp.c diff --git a/src/lib/krb5/krb/addr_srch.c b/src/lib/krb5/krb/addr_srch.c index efab59f8fd..11a3ce0bb1 100644 --- a/src/lib/krb5/krb/addr_srch.c +++ b/src/lib/krb5/krb/addr_srch.c @@ -29,6 +29,20 @@ #include "k5-int.h" +static unsigned int +address_count(krb5_address *const *addrlist) +{ + unsigned int i; + + if (addrlist == NULL) + return 0; + + for (i = 0; addrlist[i]; i++) + ; + + return i; +} + /* * if addr is listed in addrlist, or addrlist is null, return TRUE. * if not listed, return FALSE @@ -36,6 +50,14 @@ krb5_boolean krb5_address_search(krb5_context context, const krb5_address *addr, krb5_address *const *addrlist) { + /* + * Treat an address list containing only a NetBIOS address + * as empty, because we presently have no way of associating + * a client with its NetBIOS address. + */ + if (address_count(addrlist) == 1 && + addrlist[0]->addrtype == ADDRTYPE_NETBIOS) + return TRUE; if (!addrlist) return TRUE; for (; *addrlist; addrlist++) { diff --git a/src/lib/krb5/krb/auth_con.c b/src/lib/krb5/krb/auth_con.c index 8ac121a6e9..7c1858553d 100644 --- a/src/lib/krb5/krb/auth_con.c +++ b/src/lib/krb5/krb/auth_con.c @@ -34,8 +34,9 @@ krb5_auth_con_init(krb5_context context, krb5_auth_context *auth_context) (*auth_context)->req_cksumtype = context->default_ap_req_sumtype; (*auth_context)->safe_cksumtype = context->default_safe_sumtype; - (*auth_context) -> checksum_func = NULL; + (*auth_context)->checksum_func = NULL; (*auth_context)->checksum_func_data = NULL; + (*auth_context)->negotiated_etype = ENCTYPE_NULL; (*auth_context)->magic = KV5M_AUTH_CONTEXT; return 0; } diff --git a/src/lib/krb5/krb/auth_con.h b/src/lib/krb5/krb/auth_con.h index 9543de355e..be63bedbf4 100644 --- a/src/lib/krb5/krb/auth_con.h +++ b/src/lib/krb5/krb/auth_con.h @@ -21,8 +21,9 @@ struct _krb5_auth_context { krb5_pointer i_vector; /* mk_priv, rd_priv only */ krb5_rcache rcache; krb5_enctype * permitted_etypes; /* rd_req */ - krb5_mk_req_checksum_func checksum_func; - void *checksum_func_data; + krb5_mk_req_checksum_func checksum_func; + void *checksum_func_data; + krb5_enctype negotiated_etype; }; diff --git a/src/lib/krb5/krb/bld_princ.c b/src/lib/krb5/krb/bld_princ.c index 1b9a894477..c7e996374c 100644 --- a/src/lib/krb5/krb/bld_princ.c +++ b/src/lib/krb5/krb/bld_princ.c @@ -88,7 +88,7 @@ krb5int_build_principal_va(krb5_context context, princ->type = KRB5_NT_UNKNOWN; princ->magic = KV5M_PRINCIPAL; krb5_princ_set_realm_data(context, princ, r); - krb5_princ_set_realm_length(context, princ, strlen(r)); + krb5_princ_set_realm_length(context, princ, rlen); princ->data = data; princ->length = count; r = NULL; /* take ownership */ diff --git a/src/lib/krb5/krb/copy_auth.c b/src/lib/krb5/krb/copy_auth.c index 9c978cb6bf..cd27f72b52 100644 --- a/src/lib/krb5/krb/copy_auth.c +++ b/src/lib/krb5/krb/copy_auth.c @@ -26,6 +26,33 @@ * * krb5_copy_authdata() */ +/* + * Copyright (c) 2006-2008, Novell, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The copyright holder's name is not used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ #include "k5-int.h" @@ -80,3 +107,58 @@ krb5_copy_authdata(krb5_context context, krb5_authdata *const *inauthdat, krb5_a *outauthdat = tempauthdat; return 0; } + +krb5_error_code KRB5_CALLCONV +krb5_decode_authdata_container(krb5_context context, + krb5_authdatatype type, + const krb5_authdata *container, + krb5_authdata ***authdata) +{ + krb5_error_code code; + krb5_data data; + + *authdata = NULL; + + if ((container->ad_type & AD_TYPE_FIELD_TYPE_MASK) != type) + return EINVAL; + + data.length = container->length; + data.data = (char *)container->contents; + + code = decode_krb5_authdata(&data, authdata); + if (code) + return code; + + return 0; +} + +krb5_error_code KRB5_CALLCONV +krb5_encode_authdata_container(krb5_context context, + krb5_authdatatype type, + krb5_authdata *const*authdata, + krb5_authdata ***container) +{ + krb5_error_code code; + krb5_data *data; + krb5_authdata ad_datum; + krb5_authdata *ad_data[2]; + + *container = NULL; + + code = encode_krb5_authdata((krb5_authdata * const *)authdata, &data); + if (code) + return code; + + ad_datum.ad_type = type & AD_TYPE_FIELD_TYPE_MASK; + ad_datum.length = data->length; + ad_datum.contents = (unsigned char *)data->data; + + ad_data[0] = &ad_datum; + ad_data[1] = NULL; + + code = krb5_copy_authdata(context, ad_data, container); + + krb5_free_data(context, data); + + return code; +} diff --git a/src/lib/krb5/krb/gc_frm_kdc.c b/src/lib/krb5/krb/gc_frm_kdc.c index 90a49d6a6e..77b1a4232c 100644 --- a/src/lib/krb5/krb/gc_frm_kdc.c +++ b/src/lib/krb5/krb/gc_frm_kdc.c @@ -139,10 +139,6 @@ static void tr_dbg_rtree(struct tr_state *, const char *, krb5_principal); #define HARD_CC_ERR(r) ((r) && (r) != KRB5_CC_NOTFOUND && \ (r) != KRB5_CC_NOT_KTYPE) -#define IS_TGS_PRINC(c, p) \ - (krb5_princ_size((c), (p)) == 2 && \ - data_eq_string(*krb5_princ_component((c), (p), 0), KRB5_TGS_NAME)) - /* * Flags for ccache lookups of cross-realm TGTs. * @@ -169,8 +165,6 @@ static krb5_error_code do_traversal(krb5_context ctx, krb5_ccache, krb5_principal client, krb5_principal server, krb5_creds *out_cc_tgt, krb5_creds **out_tgt, krb5_creds ***out_kdc_tgts); -static krb5_error_code krb5_get_cred_from_kdc_opt(krb5_context, krb5_ccache, - krb5_creds *, krb5_creds **, krb5_creds ***, int); /* * init_cc_tgts() @@ -778,7 +772,7 @@ cleanup: * Returns errors, system errors. */ -static krb5_error_code +krb5_error_code krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache, krb5_creds *in_cred, krb5_creds **out_cred, krb5_creds ***tgts, int kdcopt) @@ -915,7 +909,7 @@ krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache, */ if (old_use_conf_ktypes || context->tgs_ktype_count == 0) goto cleanup; - for (i = 0; i < context->tgs_ktype_count; i++) { + for (i = 0; i < (signed)context->tgs_ktype_count; i++) { if ((*out_cred)->keyblock.enctype == context->tgs_ktypes[i]) { /* Found an allowable etype, so we're done */ goto cleanup; diff --git a/src/lib/krb5/krb/gc_via_tkt.c b/src/lib/krb5/krb/gc_via_tkt.c index 22ac7f9db0..c73c6d5296 100644 --- a/src/lib/krb5/krb/gc_via_tkt.c +++ b/src/lib/krb5/krb/gc_via_tkt.c @@ -31,12 +31,6 @@ #include "k5-int.h" #include "int-proto.h" -#define in_clock_skew(date, now) (labs((date)-(now)) < context->clockskew) - -#define IS_TGS_PRINC(c, p) \ - (krb5_princ_size((c), (p)) == 2 && \ - data_eq_string(*krb5_princ_component((c), (p), 0), KRB5_TGS_NAME)) - static krb5_error_code krb5_kdcrep2creds(krb5_context context, krb5_kdc_rep *pkdcrep, krb5_address *const *address, krb5_data *psectkt, krb5_creds **ppcreds) { diff --git a/src/lib/krb5/krb/gen_subkey.c b/src/lib/krb5/krb/gen_subkey.c index ad8d4bba39..4d4e7be681 100644 --- a/src/lib/krb5/krb/gen_subkey.c +++ b/src/lib/krb5/krb/gen_subkey.c @@ -40,7 +40,10 @@ key2data (krb5_keyblock k) } krb5_error_code -krb5_generate_subkey(krb5_context context, const krb5_keyblock *key, krb5_keyblock **subkey) +krb5_generate_subkey_extended(krb5_context context, + const krb5_keyblock *key, + krb5_enctype enctype, + krb5_keyblock **subkey) { krb5_error_code retval; krb5_data seed; @@ -52,10 +55,16 @@ krb5_generate_subkey(krb5_context context, const krb5_keyblock *key, krb5_keyblo if ((*subkey = (krb5_keyblock *) malloc(sizeof(krb5_keyblock))) == NULL) return(ENOMEM); - if ((retval = krb5_c_make_random_key(context, key->enctype, *subkey))) { + if ((retval = krb5_c_make_random_key(context, enctype, *subkey))) { krb5_xfree(*subkey); return(retval); } return(0); } + +krb5_error_code +krb5_generate_subkey(krb5_context context, const krb5_keyblock *key, krb5_keyblock **subkey) +{ + return krb5_generate_subkey_extended(context, key, key->enctype, subkey); +} diff --git a/src/lib/krb5/krb/get_creds.c b/src/lib/krb5/krb/get_creds.c index 38c3383170..6824a74b22 100644 --- a/src/lib/krb5/krb/get_creds.c +++ b/src/lib/krb5/krb/get_creds.c @@ -44,6 +44,7 @@ */ #include "k5-int.h" +#include "int-proto.h" static krb5_error_code krb5_get_credentials_core(krb5_context context, krb5_flags options, @@ -110,6 +111,7 @@ krb5_get_credentials(krb5_context context, krb5_flags options, krb5_creds **tgts; krb5_flags fields; int not_ktype; + int kdcopt = 0; retval = krb5_get_credentials_core(context, options, in_creds, @@ -141,7 +143,11 @@ krb5_get_credentials(krb5_context context, krb5_flags options, else not_ktype = 0; - retval = krb5_get_cred_from_kdc(context, ccache, ncreds, out_creds, &tgts); + if (options & KRB5_GC_CANONICALIZE) + kdcopt |= KDC_OPT_CANONICALIZE; + + retval = krb5_get_cred_from_kdc_opt(context, ccache, ncreds, + out_creds, &tgts, kdcopt); if (tgts) { register int i = 0; krb5_error_code rv2; diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c index a1c29a46aa..f30ae21227 100644 --- a/src/lib/krb5/krb/get_in_tkt.c +++ b/src/lib/krb5/krb/get_in_tkt.c @@ -293,15 +293,31 @@ verify_as_reply(krb5_context context, krb5_kdc_rep *as_reply) { krb5_error_code retval; - + int canon_req; + int canon_ok; + /* check the contents for sanity: */ if (!as_reply->enc_part2->times.starttime) as_reply->enc_part2->times.starttime = as_reply->enc_part2->times.authtime; - - if (!krb5_principal_compare(context, as_reply->client, request->client) - || !krb5_principal_compare(context, as_reply->enc_part2->server, request->server) - || !krb5_principal_compare(context, as_reply->ticket->server, request->server) + + /* + * We only allow the AS-REP server name to be changed if the + * caller set the canonicalize flag (or requested an enterprise + * principal) and we requested (and received) a TGT. + */ + canon_req = ((request->kdc_options & KDC_OPT_CANONICALIZE) != 0) || + (krb5_princ_type(context, request->client) == KRB5_NT_ENTERPRISE_PRINCIPAL); + if (canon_req) { + canon_ok = IS_TGS_PRINC(context, request->server) && + IS_TGS_PRINC(context, as_reply->enc_part2->server); + } else + canon_ok = 0; + + if ((!canon_ok && + (!krb5_principal_compare(context, as_reply->client, request->client) || + !krb5_principal_compare(context, as_reply->enc_part2->server, request->server))) + || !krb5_principal_compare(context, as_reply->enc_part2->server, as_reply->ticket->server) || (request->nonce != as_reply->enc_part2->nonce) /* XXX check for extraneous flags */ /* XXX || (!krb5_addresses_compare(context, addrs, as_reply->enc_part2->caddrs)) */ @@ -507,7 +523,10 @@ krb5_get_in_tkt(krb5_context context, krb5_pa_data ** preauth_to_use = 0; int loopcount = 0; krb5_int32 do_more = 0; + int canon_flag; int use_master = 0; + int referral_count = 0; + krb5_principal_data referred_client; #if APPLE_PKINIT inTktDebug("krb5_get_in_tkt top\n"); @@ -518,7 +537,15 @@ krb5_get_in_tkt(krb5_context context, if (ret_as_reply) *ret_as_reply = 0; - + + referred_client = *(creds->client); + referred_client.realm.data = NULL; + referred_client.realm.length = 0; + + /* per referrals draft, enterprise principals imply canonicalization */ + canon_flag = ((options & KDC_OPT_CANONICALIZE) != 0) || + creds->client->type == KRB5_NT_ENTERPRISE_PRINCIPAL; + /* * Set up the basic request structure */ @@ -641,6 +668,27 @@ krb5_get_in_tkt(krb5_context context, if (retval) goto cleanup; continue; + } else if (canon_flag && err_reply->error == KDC_ERR_WRONG_REALM) { + if (++referral_count > KRB5_REFERRAL_MAXHOPS || + err_reply->client == NULL || + err_reply->client->realm.length == 0) { + retval = KRB5KDC_ERR_WRONG_REALM; + krb5_free_error(context, err_reply); + goto cleanup; + } + /* Rewrite request.client with realm from error reply */ + if (referred_client.realm.data) { + krb5_free_data_contents(context, &referred_client.realm); + referred_client.realm.data = NULL; + } + retval = krb5int_copy_data_contents(context, + &err_reply->client->realm, + &referred_client.realm); + krb5_free_error(context, err_reply); + if (retval) + goto cleanup; + request.client = &referred_client; + continue; } else { retval = (krb5_error_code) err_reply->error + ERROR_TABLE_BASE_krb5; @@ -692,6 +740,8 @@ cleanup: else krb5_free_kdc_rep(context, as_reply); } + if (referred_client.realm.data) + krb5_free_data_contents(context, &referred_client.realm); return (retval); } @@ -923,6 +973,8 @@ krb5_get_init_creds(krb5_context context, krb5_timestamp time_now; krb5_enctype etype = 0; krb5_preauth_client_rock get_data_rock; + int canon_flag = 0; + krb5_principal_data referred_client; /* initialize everything which will be freed at cleanup */ @@ -947,6 +999,11 @@ krb5_get_init_creds(krb5_context context, err_reply = NULL; + /* referred_client is used to rewrite the client realm for referrals */ + referred_client = *client; + referred_client.realm.data = NULL; + referred_client.realm.length = 0; + /* * Set up the basic request structure */ @@ -984,6 +1041,17 @@ krb5_get_init_creds(krb5_context context, if (tempint) request.kdc_options |= KDC_OPT_PROXIABLE; + /* canonicalize */ + if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_CANONICALIZE)) + tempint = 1; + else if ((ret = krb5_libdefault_boolean(context, &client->realm, + "canonicalize", &tempint)) == 0) + ; + else + tempint = 0; + if (tempint) + request.kdc_options |= KDC_OPT_CANONICALIZE; + /* allow_postdate */ if (start_time > 0) @@ -1045,6 +1113,10 @@ krb5_get_init_creds(krb5_context context, request.client = client; + /* per referrals draft, enterprise principals imply canonicalization */ + canon_flag = ((request.kdc_options & KDC_OPT_CANONICALIZE) != 0) || + client->type == KRB5_NT_ENTERPRISE_PRINCIPAL; + /* service */ if (in_tkt_service) { @@ -1151,7 +1223,7 @@ krb5_get_init_creds(krb5_context context, krb5_data random_data; random_data.length = 4; - random_data.data = random_buf; + random_data.data = (char *)random_buf; if (krb5_c_random_make_octets(context, &random_data) == 0) /* See RT ticket 3196 at MIT. If we set the high bit, we may have compatibility problems with Heimdal, because @@ -1253,6 +1325,25 @@ krb5_get_init_creds(krb5_context context, if (ret) goto cleanup; /* continue to next iteration */ + } else if (canon_flag && err_reply->error == KDC_ERR_WRONG_REALM) { + if (err_reply->client == NULL || + err_reply->client->realm.length == 0) { + ret = KRB5KDC_ERR_WRONG_REALM; + krb5_free_error(context, err_reply); + goto cleanup; + } + /* Rewrite request.client with realm from error reply */ + if (referred_client.realm.data) { + krb5_free_data_contents(context, &referred_client.realm); + referred_client.realm.data = NULL; + } + ret = krb5int_copy_data_contents(context, + &err_reply->client->realm, + &referred_client.realm); + krb5_free_error(context, err_reply); + if (ret) + goto cleanup; + request.client = &referred_client; } else { if (err_reply->e_data.length > 0) { /* continue to next iteration */ @@ -1403,6 +1494,8 @@ cleanup: *as_reply = local_as_reply; else if (local_as_reply) krb5_free_kdc_rep(context, local_as_reply); + if (referred_client.realm.data) + krb5_free_data_contents(context, &referred_client.realm); return(ret); } diff --git a/src/lib/krb5/krb/gic_opt.c b/src/lib/krb5/krb/gic_opt.c index 1ba1877bf9..348637ca3c 100644 --- a/src/lib/krb5/krb/gic_opt.c +++ b/src/lib/krb5/krb/gic_opt.c @@ -43,6 +43,15 @@ krb5_get_init_creds_opt_set_proxiable(krb5_get_init_creds_opt *opt, int proxiabl } void KRB5_CALLCONV +krb5_get_init_creds_opt_set_canonicalize(krb5_get_init_creds_opt *opt, int canonicalize) +{ + if (canonicalize) + opt->flags |= KRB5_GET_INIT_CREDS_OPT_CANONICALIZE; + else + opt->flags &= ~(KRB5_GET_INIT_CREDS_OPT_CANONICALIZE); +} + +void KRB5_CALLCONV krb5_get_init_creds_opt_set_etype_list(krb5_get_init_creds_opt *opt, krb5_enctype *etype_list, int etype_list_length) { opt->flags |= KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST; diff --git a/src/lib/krb5/krb/int-proto.h b/src/lib/krb5/krb/int-proto.h index 5c576c3fd6..b81fe2566b 100644 --- a/src/lib/krb5/krb/int-proto.h +++ b/src/lib/krb5/krb/int-proto.h @@ -54,5 +54,16 @@ krb5_preauth_supply_preauth_data(krb5_context context, const char *attr, const char *value); +krb5_error_code +krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache, + krb5_creds *in_cred, krb5_creds **out_cred, + krb5_creds ***tgts, int kdcopt); + +#define in_clock_skew(date, now) (labs((date)-(now)) < context->clockskew) + +#define IS_TGS_PRINC(c, p) \ + (krb5_princ_size((c), (p)) == 2 && \ + data_eq_string(*krb5_princ_component((c), (p), 0), KRB5_TGS_NAME)) + #endif /* KRB5_INT_FUNC_PROTO__ */ diff --git a/src/lib/krb5/krb/kfree.c b/src/lib/krb5/krb/kfree.c index 39bb1f2128..9e5e192748 100644 --- a/src/lib/krb5/krb/kfree.c +++ b/src/lib/krb5/krb/kfree.c @@ -25,6 +25,33 @@ * * krb5_free_address() */ +/* + * Copyright (c) 2006-2008, Novell, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The copyright holder's name is not used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ #include "k5-int.h" @@ -713,3 +740,72 @@ krb5_free_pa_enc_ts(krb5_context ctx, krb5_pa_enc_ts *pa_enc_ts) return; krb5_xfree(pa_enc_ts); } + +void KRB5_CALLCONV +krb5_free_pa_for_user(krb5_context context, krb5_pa_for_user *req) +{ + if (req == NULL) + return; + if (req->user != NULL) { + krb5_free_principal(context, req->user); + req->user = NULL; + } + krb5_free_checksum_contents(context, &req->cksum); + krb5_free_data_contents(context, &req->auth_package); + krb5_xfree(req); +} + +void KRB5_CALLCONV +krb5_free_pa_server_referral_data(krb5_context context, + krb5_pa_server_referral_data *ref) +{ + if (ref == NULL) + return; + if (ref->referred_realm) { + krb5_free_data(context, ref->referred_realm); + ref->referred_realm = NULL; + } + if (ref->true_principal_name != NULL) { + krb5_free_principal(context, ref->true_principal_name); + ref->true_principal_name = NULL; + } + if (ref->requested_principal_name != NULL) { + krb5_free_principal(context, ref->requested_principal_name); + ref->requested_principal_name = NULL; + } + krb5_free_checksum_contents(context, &ref->rep_cksum); + krb5_xfree(ref); +} + +void KRB5_CALLCONV +krb5_free_pa_svr_referral_data(krb5_context context, + krb5_pa_svr_referral_data *ref) +{ + if (ref == NULL) + return; + if (ref->principal != NULL) { + krb5_free_principal(context, ref->principal); + ref->principal = NULL; + } + krb5_xfree(ref); +} + +void KRB5_CALLCONV +krb5_free_pa_pac_req(krb5_context context, + krb5_pa_pac_req *req) +{ + if (req == NULL) + return; + krb5_xfree(req); +} + +void KRB5_CALLCONV +krb5_free_etype_list(krb5_context context, + krb5_etype_list *etypes) +{ + if (etypes != NULL) { + if (etypes->etypes != NULL) + krb5_xfree(etypes->etypes); + krb5_xfree(etypes); + } +} diff --git a/src/lib/krb5/krb/mk_cred.c b/src/lib/krb5/krb/mk_cred.c index e9ed3850bd..8611e14097 100644 --- a/src/lib/krb5/krb/mk_cred.c +++ b/src/lib/krb5/krb/mk_cred.c @@ -174,13 +174,15 @@ krb5_mk_ncred(krb5_context context, krb5_auth_context auth_context, /* * Allocate memory for a NULL terminated list of tickets. */ - for (ncred = 0; ppcreds[ncred]; ncred++); + for (ncred = 0; ppcreds[ncred]; ncred++) + ; if ((pcred = (krb5_cred *)calloc(1, sizeof(krb5_cred))) == NULL) return ENOMEM; if ((pcred->tickets - = (krb5_ticket **)calloc(ncred+1, sizeof(krb5_ticket *))) == NULL) { + = (krb5_ticket **)calloc((size_t)ncred+1, + sizeof(krb5_ticket *))) == NULL) { free(pcred); return ENOMEM; } diff --git a/src/lib/krb5/krb/mk_rep.c b/src/lib/krb5/krb/mk_rep.c index b512f09752..ee4f34ed2f 100644 --- a/src/lib/krb5/krb/mk_rep.c +++ b/src/lib/krb5/krb/mk_rep.c @@ -26,6 +26,33 @@ * * krb5_mk_rep() */ +/* + * Copyright (c) 2006-2008, Novell, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The copyright holder's name is not used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ #include "k5-int.h" #include "auth_con.h" @@ -39,9 +66,9 @@ returns system errors */ -krb5_error_code KRB5_CALLCONV -krb5_mk_rep(krb5_context context, krb5_auth_context auth_context, - krb5_data *outbuf) +static krb5_error_code +k5_mk_rep(krb5_context context, krb5_auth_context auth_context, + krb5_data *outbuf, int dce_style) { krb5_error_code retval; krb5_ap_rep_enc_part repl; @@ -58,17 +85,31 @@ krb5_mk_rep(krb5_context context, krb5_auth_context auth_context, return(retval); } - repl.ctime = auth_context->authentp->ctime; - repl.cusec = auth_context->authentp->cusec; - if (auth_context->auth_context_flags & KRB5_AUTH_CONTEXT_USE_SUBKEY) { + if (dce_style) { + krb5_us_timeofday(context, &repl.ctime, &repl.cusec); + } else { + repl.ctime = auth_context->authentp->ctime; + repl.cusec = auth_context->authentp->cusec; + } + + if (dce_style) + repl.subkey = NULL; + else if (auth_context->auth_context_flags & KRB5_AUTH_CONTEXT_USE_SUBKEY) { + assert(auth_context->negotiated_etype != ENCTYPE_NULL); + retval = krb5int_generate_and_save_subkey (context, auth_context, - auth_context->keyblock); + auth_context->keyblock, + auth_context->negotiated_etype); if (retval) return retval; repl.subkey = auth_context->send_subkey; } else repl.subkey = auth_context->authentp->subkey; - repl.seq_number = auth_context->local_seq_number; + + if (dce_style) + repl.seq_number = auth_context->remote_seq_number; + else + repl.seq_number = auth_context->local_seq_number; /* encode it before encrypting */ if ((retval = encode_krb5_ap_rep_enc_part(&repl, &scratch))) @@ -95,3 +136,15 @@ cleanup_scratch: return retval; } + +krb5_error_code KRB5_CALLCONV +krb5_mk_rep(krb5_context context, krb5_auth_context auth_context, krb5_data *outbuf) +{ + return k5_mk_rep(context, auth_context, outbuf, 0); +} + +krb5_error_code KRB5_CALLCONV +krb5_mk_rep_dce(krb5_context context, krb5_auth_context auth_context, krb5_data *outbuf) +{ + return k5_mk_rep(context, auth_context, outbuf, 1); +} diff --git a/src/lib/krb5/krb/mk_req_ext.c b/src/lib/krb5/krb/mk_req_ext.c index 6d40e5de7a..4a9d03551f 100644 --- a/src/lib/krb5/krb/mk_req_ext.c +++ b/src/lib/krb5/krb/mk_req_ext.c @@ -64,16 +64,25 @@ returns system errors */ +static krb5_error_code +make_etype_list(krb5_context context, + krb5_enctype *desired_etypes, + krb5_enctype tkt_enctype, + krb5_authdata ***authdata); + static krb5_error_code krb5_generate_authenticator (krb5_context, krb5_authenticator *, krb5_principal, krb5_checksum *, krb5_keyblock *, - krb5_ui_4, krb5_authdata ** ); + krb5_ui_4, krb5_authdata **, + krb5_enctype *desired_etypes, + krb5_enctype tkt_enctype); krb5_error_code krb5int_generate_and_save_subkey (krb5_context context, krb5_auth_context auth_context, - krb5_keyblock *keyblock) + krb5_keyblock *keyblock, + krb5_enctype enctype) { /* Provide some more fodder for random number code. This isn't strong cryptographically; the point here is not @@ -92,7 +101,8 @@ krb5int_generate_and_save_subkey (krb5_context context, if (auth_context->send_subkey) krb5_free_keyblock(context, auth_context->send_subkey); - if ((retval = krb5_generate_subkey(context, keyblock, &auth_context->send_subkey))) + if ((retval = krb5_generate_subkey_extended(context, keyblock, enctype, + &auth_context->send_subkey))) return retval; if (auth_context->recv_subkey) @@ -116,18 +126,23 @@ krb5_mk_req_extended(krb5_context context, krb5_auth_context *auth_context, krb5_checksum checksum; krb5_checksum *checksump = 0; krb5_auth_context new_auth_context; + krb5_enctype *desired_etypes = NULL; krb5_ap_req request; krb5_data *scratch = 0; krb5_data *toutbuf; request.ap_options = ap_req_options & AP_OPTS_WIRE_MASK; - request.authenticator.ciphertext.data = 0; + request.authenticator.ciphertext.data = NULL; request.ticket = 0; if (!in_creds->ticket.length) return(KRB5_NO_TKT_SUPPLIED); + if ((ap_req_options & AP_OPTS_ETYPE_NEGOTIATION) && + !(ap_req_options & AP_OPTS_MUTUAL_REQUIRED)) + return(EINVAL); + /* we need a native ticket */ if ((retval = decode_krb5_ticket(&(in_creds)->ticket, &request.ticket))) return(retval); @@ -174,7 +189,8 @@ krb5_mk_req_extended(krb5_context context, krb5_auth_context *auth_context, if ((ap_req_options & AP_OPTS_USE_SUBKEY)&&(!(*auth_context)->send_subkey)) { retval = krb5int_generate_and_save_subkey (context, *auth_context, - &in_creds->keyblock); + &in_creds->keyblock, + in_creds->keyblock.enctype); if (retval) goto cleanup; } @@ -205,12 +221,23 @@ krb5_mk_req_extended(krb5_context context, krb5_auth_context *auth_context, goto cleanup_cksum; } + if (ap_req_options & AP_OPTS_ETYPE_NEGOTIATION) { + if ((*auth_context)->permitted_etypes == NULL) { + retval = krb5_get_tgs_ktypes(context, in_creds->server, &desired_etypes); + if (retval) + goto cleanup_cksum; + } else + desired_etypes = (*auth_context)->permitted_etypes; + } + if ((retval = krb5_generate_authenticator(context, (*auth_context)->authentp, - (in_creds)->client, checksump, + in_creds->client, checksump, (*auth_context)->send_subkey, (*auth_context)->local_seq_number, - (in_creds)->authdata))) + in_creds->authdata, + desired_etypes, + in_creds->keyblock.enctype))) goto cleanup_cksum; /* encode the authenticator */ @@ -223,7 +250,6 @@ krb5_mk_req_extended(krb5_context context, krb5_auth_context *auth_context, */ (*auth_context)->authentp->client = NULL; (*auth_context)->authentp->checksum = NULL; - (*auth_context)->authentp->authorization_data = NULL; /* call the encryption routine */ if ((retval = krb5_encrypt_helper(context, &in_creds->keyblock, @@ -242,6 +268,9 @@ cleanup_cksum: free(checksump->contents); cleanup: + if (desired_etypes && + desired_etypes != (*auth_context)->permitted_etypes) + krb5_xfree(desired_etypes); if (request.ticket) krb5_free_ticket(context, request.ticket); if (request.authenticator.ciphertext.data) { @@ -261,7 +290,9 @@ static krb5_error_code krb5_generate_authenticator(krb5_context context, krb5_authenticator *authent, krb5_principal client, krb5_checksum *cksum, krb5_keyblock *key, krb5_ui_4 seq_number, - krb5_authdata **authorization) + krb5_authdata **authorization, + krb5_enctype *desired_etypes, + krb5_enctype tkt_enctype) { krb5_error_code retval; @@ -274,7 +305,116 @@ krb5_generate_authenticator(krb5_context context, krb5_authenticator *authent, } else authent->subkey = 0; authent->seq_number = seq_number; - authent->authorization_data = authorization; + authent->authorization_data = NULL; + + if (authorization != NULL) { + retval = krb5_copy_authdata(context, authorization, + &authent->authorization_data); + if (retval) + return retval; + } + /* Only send EtypeList if we prefer another enctype to tkt_enctype */ + if (desired_etypes != NULL && desired_etypes[0] != tkt_enctype) { + retval = make_etype_list(context, desired_etypes, tkt_enctype, + &authent->authorization_data); + if (retval) + return retval; + } return(krb5_us_timeofday(context, &authent->ctime, &authent->cusec)); } + +/* RFC 4537 */ +static krb5_error_code +make_etype_list(krb5_context context, + krb5_enctype *desired_etypes, + krb5_enctype tkt_enctype, + krb5_authdata ***authdata) +{ + krb5_error_code code; + krb5_etype_list etypes; + krb5_data *enc_etype_list; + krb5_data *ad_if_relevant; + krb5_authdata *etype_adata[2], etype_adatum, **adata; + int i; + + etypes.etypes = desired_etypes; + + for (etypes.length = 0; + etypes.etypes[etypes.length] != ENCTYPE_NULL; + etypes.length++) + ; + + /* + * RFC 4537: + * + * If the enctype of the ticket session key is included in the enctype + * list sent by the client, it SHOULD be the last on the list; + */ + for (i = 0; i < etypes.length; i++) { + if (etypes.etypes[i] == tkt_enctype) { + krb5_enctype etype; + + etype = etypes.etypes[etypes.length - 1]; + etypes.etypes[etypes.length - 1] = tkt_enctype; + etypes.etypes[i] = etype; + break; + } + } + + code = encode_krb5_etype_list(&etypes, &enc_etype_list); + if (code) { + return code; + } + + etype_adatum.magic = KV5M_AUTHDATA; + etype_adatum.ad_type = KRB5_AUTHDATA_ETYPE_NEGOTIATION; + etype_adatum.length = enc_etype_list->length; + etype_adatum.contents = (krb5_octet *)enc_etype_list->data; + + etype_adata[0] = &etype_adatum; + etype_adata[1] = NULL; + + /* Wrap in AD-IF-RELEVANT container */ + code = encode_krb5_authdata(etype_adata, &ad_if_relevant); + if (code) { + krb5_free_data(context, enc_etype_list); + return code; + } + + krb5_free_data(context, enc_etype_list); + + adata = *authdata; + if (adata == NULL) { + adata = (krb5_authdata **)calloc(2, sizeof(krb5_authdata *)); + i = 0; + } else { + for (i = 0; adata[i] != NULL; i++) + ; + + adata = (krb5_authdata **)realloc(*authdata, + (i + 2) * sizeof(krb5_authdata *)); + } + if (adata == NULL) { + krb5_free_data(context, ad_if_relevant); + return ENOMEM; + } + + adata[i] = (krb5_authdata *)malloc(sizeof(krb5_authdata)); + if (adata[i] == NULL) { + krb5_free_data(context, ad_if_relevant); + return ENOMEM; + } + adata[i]->magic = KV5M_AUTHDATA; + adata[i]->ad_type = KRB5_AUTHDATA_IF_RELEVANT; + adata[i]->length = ad_if_relevant->length; + adata[i]->contents = (krb5_octet *)ad_if_relevant->data; + krb5_xfree(ad_if_relevant); /* contents owned by adata[i] */ + + adata[i + 1] = NULL; + + *authdata = adata; + + return 0; +} + diff --git a/src/lib/krb5/krb/pac.c b/src/lib/krb5/krb/pac.c new file mode 100644 index 0000000000..9aaff954f3 --- /dev/null +++ b/src/lib/krb5/krb/pac.c @@ -0,0 +1,869 @@ +/* + * lib/krb5/krb/pac.c + * + * Copyright 2008 by the Massachusetts Institute of Technology. + * All Rights Reserved. + * + * Export of this software from the United States of America may + * require a specific license from the United States Government. + * It is the responsibility of any person or organization contemplating + * export to obtain such a license before exporting. + * + * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + * distribute this software and its documentation for any purpose and + * without fee is hereby granted, provided that the above copyright + * notice appear in all copies and that both that copyright notice and + * this permission notice appear in supporting documentation, and that + * the name of M.I.T. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. Furthermore if you modify this software you must label + * your software as modified software and not distribute it in such a + * fashion that it might be confused with the original M.I.T. software. + * M.I.T. makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + */ + +#include "k5-int.h" +#include "k5-utf8.h" + +/* draft-brezak-win2k-krb-authz-00 */ + +/* + * A PAC consists of a sequence of PAC_INFO_BUFFERs, preceeded by + * a PACTYPE header. Decoding the contents of the buffers is left + * to the application (notwithstanding signature verification). + */ + +typedef struct _PAC_INFO_BUFFER { + krb5_ui_4 ulType; + krb5_ui_4 cbBufferSize; + krb5_ui_8 Offset; +} PAC_INFO_BUFFER; + +#define PAC_INFO_BUFFER_LENGTH 16 + +/* ulType */ +#define PAC_LOGON_INFO 1 +#define PAC_SERVER_CHECKSUM 6 +#define PAC_PRIVSVR_CHECKSUM 7 +#define PAC_CLIENT_INFO 10 + +typedef struct _PACTYPE { + krb5_ui_4 cBuffers; + krb5_ui_4 Version; + PAC_INFO_BUFFER Buffers[1]; +} PACTYPE; + +#define PAC_ALIGNMENT 8 +#define PACTYPE_LENGTH 8U +#define PAC_SIGNATURE_DATA_LENGTH 4U +#define PAC_CLIENT_INFO_LENGTH 10U + +#define NT_TIME_EPOCH 11644473600LL + +struct krb5_pac_data { + PACTYPE *pac; /* PAC header + info buffer array */ + krb5_data data; /* PAC data (including uninitialised header) */ +}; + +static krb5_error_code +k5_pac_locate_buffer(krb5_context context, + const krb5_pac pac, + krb5_ui_4 type, + krb5_data *data); + +/* + * Add a buffer to the provided PAC and update header. + */ +static krb5_error_code +k5_pac_add_buffer(krb5_context context, + krb5_pac pac, + krb5_ui_4 type, + const krb5_data *data, + krb5_boolean zerofill, + krb5_data *out_data) +{ + PACTYPE *header; + size_t header_len, i, pad = 0; + char *pac_data; + + assert((data->data == NULL) == zerofill); + + /* Check there isn't already a buffer of this type */ + if (k5_pac_locate_buffer(context, pac, type, NULL) == 0) { + return EINVAL; + } + + header = (PACTYPE *)realloc(pac->pac, + sizeof(PACTYPE) + + (pac->pac->cBuffers * sizeof(PAC_INFO_BUFFER))); + if (header == NULL) { + return ENOMEM; + } + pac->pac = header; + + header_len = PACTYPE_LENGTH + (pac->pac->cBuffers * PAC_INFO_BUFFER_LENGTH); + + if (data->length % PAC_ALIGNMENT) + pad = PAC_ALIGNMENT - (data->length % PAC_ALIGNMENT); + + pac_data = realloc(pac->data.data, + pac->data.length + PAC_INFO_BUFFER_LENGTH + data->length + pad); + if (pac_data == NULL) { + return ENOMEM; + } + pac->data.data = pac_data; + + /* Update offsets of existing buffers */ + for (i = 0; i < pac->pac->cBuffers; i++) + pac->pac->Buffers[i].Offset += PAC_INFO_BUFFER_LENGTH; + + /* Make room for new PAC_INFO_BUFFER */ + memmove(pac->data.data + header_len + PAC_INFO_BUFFER_LENGTH, + pac->data.data + header_len, + pac->data.length - header_len); + memset(pac->data.data + header_len, 0, PAC_INFO_BUFFER_LENGTH); + + /* Initialise new PAC_INFO_BUFFER */ + pac->pac->Buffers[i].ulType = type; + pac->pac->Buffers[i].cbBufferSize = data->length; + pac->pac->Buffers[i].Offset = pac->data.length + PAC_INFO_BUFFER_LENGTH; + assert((pac->pac->Buffers[i].Offset % PAC_ALIGNMENT) == 0); + + /* Copy in new PAC data and zero padding bytes */ + if (zerofill) + memset(pac->data.data + pac->pac->Buffers[i].Offset, 0, data->length); + else + memcpy(pac->data.data + pac->pac->Buffers[i].Offset, data->data, data->length); + + memset(pac->data.data + pac->pac->Buffers[i].Offset + data->length, 0, pad); + + pac->pac->cBuffers++; + pac->data.length += PAC_INFO_BUFFER_LENGTH + data->length + pad; + + if (out_data != NULL) { + out_data->data = pac->data.data + pac->pac->Buffers[i].Offset; + out_data->length = data->length; + } + + return 0; +} + +krb5_error_code KRB5_CALLCONV +krb5_pac_add_buffer(krb5_context context, + krb5_pac pac, + krb5_ui_4 type, + const krb5_data *data) +{ + return k5_pac_add_buffer(context, pac, type, data, FALSE, NULL); +} + +/* + * Free a PAC + */ +void KRB5_CALLCONV +krb5_pac_free(krb5_context context, + krb5_pac pac) +{ + if (pac != NULL) { + if (pac->data.data != NULL) { + memset(pac->data.data, 0, pac->data.length); + free(pac->data.data); + } + if (pac->pac != NULL) + free(pac->pac); + memset(pac, 0, sizeof(*pac)); + free(pac); + } +} + +static krb5_error_code +k5_pac_locate_buffer(krb5_context context, + const krb5_pac pac, + krb5_ui_4 type, + krb5_data *data) +{ + PAC_INFO_BUFFER *buffer = NULL; + size_t i; + + if (pac == NULL) + return EINVAL; + + for (i = 0; i < pac->pac->cBuffers; i++) { + if (pac->pac->Buffers[i].ulType == type) { + if (buffer == NULL) + buffer = &pac->pac->Buffers[i]; + else + return EINVAL; + } + } + + if (buffer == NULL) + return ENOENT; + + assert(buffer->Offset + buffer->cbBufferSize <= pac->data.length); + + if (data != NULL) { + data->length = buffer->cbBufferSize; + data->data = pac->data.data + buffer->Offset; + } + + return 0; +} + +/* + * Find a buffer and copy data into output + */ +krb5_error_code KRB5_CALLCONV +krb5_pac_get_buffer(krb5_context context, + krb5_pac pac, + krb5_ui_4 type, + krb5_data *data) +{ + krb5_data d; + krb5_error_code ret; + + ret = k5_pac_locate_buffer(context, pac, type, &d); + if (ret != 0) + return ret; + + data->data = malloc(d.length); + if (data == NULL) + return ENOMEM; + + data->length = d.length; + memcpy(data->data, d.data, d.length); + + return 0; +} + +/* + * Return an array of the types of data in the PAC + */ +krb5_error_code KRB5_CALLCONV +krb5_pac_get_types(krb5_context context, + krb5_pac pac, + size_t *len, + krb5_ui_4 **types) +{ + size_t i; + + *types = (krb5_ui_4 *)malloc(pac->pac->cBuffers * sizeof(krb5_ui_4)); + if (*types == NULL) + return ENOMEM; + + *len = pac->pac->cBuffers; + + for (i = 0; i < pac->pac->cBuffers; i++) + (*types)[i] = pac->pac->Buffers[i].ulType; + + return 0; +} + +/* + * Initialize PAC + */ +krb5_error_code KRB5_CALLCONV +krb5_pac_init(krb5_context context, + krb5_pac *ppac) +{ + krb5_pac pac; + + pac = (krb5_pac)malloc(sizeof(*pac)); + if (pac == NULL) + return ENOMEM; + + pac->pac = (PACTYPE *)malloc(sizeof(PACTYPE)); + if (pac->pac == NULL) { + free( pac); + return ENOMEM; + } + + pac->pac->cBuffers = 0; + pac->pac->Version = 0; + + pac->data.length = PACTYPE_LENGTH; + pac->data.data = calloc(1, pac->data.length); + if (pac->data.data == NULL) { + krb5_pac_free(context, pac); + return ENOMEM; + } + + *ppac = pac; + + return 0; +} + +/* + * Parse the supplied data into the PAC allocated by this function + */ +krb5_error_code KRB5_CALLCONV +krb5_pac_parse(krb5_context context, + const void *ptr, + size_t len, + krb5_pac *ppac) +{ + krb5_error_code ret; + size_t i; + PACTYPE header; + const unsigned char *p = (const unsigned char *)ptr; + krb5_pac pac; + size_t header_len; + + *ppac = NULL; + + if (len < PACTYPE_LENGTH) + return ERANGE; + + header.cBuffers = load_32_le(p); + p += 4; + header.Version = load_32_le(p); + p += 4; + + if (header.Version != 0) + return EINVAL; + + header_len = PACTYPE_LENGTH + (header.cBuffers * PAC_INFO_BUFFER_LENGTH); + if (len < header_len) + return ERANGE; + + ret = krb5_pac_init(context, &pac); + if (ret != 0) + return ret; + + pac->pac = (PACTYPE *)realloc(pac->pac, + sizeof(PACTYPE) + ((header.cBuffers - 1) * sizeof(PAC_INFO_BUFFER))); + if (pac->pac == NULL) { + krb5_pac_free(context, pac); + return ENOMEM; + } + + memcpy(pac->pac, &header, sizeof(header)); + + for (i = 0; i < pac->pac->cBuffers; i++) { + PAC_INFO_BUFFER *buffer = &pac->pac->Buffers[i]; + + buffer->ulType = load_32_le(p); + p += 4; + buffer->cbBufferSize = load_32_le(p); + p += 4; + buffer->Offset = load_64_le(p); + p += 8; + + if (buffer->Offset % PAC_ALIGNMENT) { + krb5_pac_free(context, pac); + return EINVAL; + } + if (buffer->Offset < header_len || + buffer->Offset + buffer->cbBufferSize > len) { + krb5_pac_free(context, pac); + return ERANGE; + } + } + + pac->data.data = realloc(pac->data.data, len); + if (pac->data.data == NULL) { + krb5_pac_free(context, pac); + return ENOMEM; + } + memcpy(pac->data.data, ptr, len); + + pac->data.length = len; + + *ppac = pac; + + return 0; +} + +static krb5_error_code +k5_time_to_seconds_since_1970(krb5_ui_8 ntTime, krb5_timestamp *elapsedSeconds) +{ + krb5_ui_8 abstime; + + ntTime /= 10000000; + + abstime = time > 0 ? ntTime - NT_TIME_EPOCH : -ntTime; + + if (abstime > KRB5_INT32_MAX) + return ERANGE; + + *elapsedSeconds = abstime; + + return 0; +} + +static krb5_error_code +k5_seconds_since_1970_to_time(krb5_timestamp elapsedSeconds, krb5_ui_8 *ntTime) +{ + *ntTime = elapsedSeconds; + + if (elapsedSeconds > 0) + *ntTime += NT_TIME_EPOCH; + + *ntTime *= 10000000; + + return 0; +} + +static krb5_error_code +k5_pac_validate_client(krb5_context context, + const krb5_pac pac, + krb5_timestamp authtime, + krb5_const_principal principal) +{ + krb5_error_code ret; + krb5_data client_info; + char *pac_princname; + unsigned char *p; + krb5_timestamp pac_authtime; + krb5_ui_2 pac_princname_length; + krb5_ui_8 pac_nt_authtime; + krb5_principal pac_principal; + + ret = k5_pac_locate_buffer(context, pac, PAC_CLIENT_INFO, &client_info); + if (ret != 0) + return ret; + + if (client_info.length < PAC_CLIENT_INFO_LENGTH) + return ERANGE; + + p = (unsigned char *)client_info.data; + pac_nt_authtime = load_64_le(p); + p += 8; + pac_princname_length = load_16_le(p); + p += 2; + + ret = k5_time_to_seconds_since_1970(pac_nt_authtime, &pac_authtime); + if (ret != 0) + return ret; + + if (client_info.length < PAC_CLIENT_INFO_LENGTH + pac_princname_length || + pac_princname_length % 2) + return ERANGE; + + ret = krb5int_ucs2lecs_to_utf8s(p, (size_t)pac_princname_length / 2, &pac_princname, NULL); + if (ret != 0) + return ret; + + ret = krb5_parse_name_flags(context, pac_princname, 0, &pac_principal); + if (ret != 0) { + free(pac_princname); + return ret; + } + + free(pac_princname); + + if (pac_authtime != authtime || + krb5_principal_compare(context, pac_principal, principal) == FALSE) + ret = KRB5KRB_AP_WRONG_PRINC; + + krb5_free_principal(context, pac_principal); + + return ret; +} + +static krb5_error_code +k5_pac_zero_signature(krb5_context context, + const krb5_pac pac, + krb5_ui_4 type, + krb5_data *data) +{ + PAC_INFO_BUFFER *buffer = NULL; + size_t i; + + assert(type == PAC_SERVER_CHECKSUM || type == PAC_PRIVSVR_CHECKSUM); + assert(data->length >= pac->data.length); + + for (i = 0; i < pac->pac->cBuffers; i++) { + if (pac->pac->Buffers[i].ulType == type) { + buffer = &pac->pac->Buffers[i]; + break; + } + } + + if (buffer == NULL) + return ENOENT; + + if (buffer->Offset + buffer->cbBufferSize > pac->data.length) + return ERANGE; + + if (buffer->cbBufferSize < PAC_SIGNATURE_DATA_LENGTH) + return KRB5_BAD_MSIZE; + + /* Zero out the data portion of the checksum only */ + memset(data->data + buffer->Offset + PAC_SIGNATURE_DATA_LENGTH, + 0, + buffer->cbBufferSize - PAC_SIGNATURE_DATA_LENGTH); + + return 0; +} + +static krb5_error_code +k5_pac_verify_server_checksum(krb5_context context, + const krb5_pac pac, + const krb5_keyblock *server) +{ + krb5_error_code ret; + krb5_data pac_data; /* PAC with zeroed checksums */ + krb5_checksum checksum; + krb5_data checksum_data; + krb5_boolean valid; + krb5_octet *p; + + ret = k5_pac_locate_buffer(context, pac, PAC_SERVER_CHECKSUM, &checksum_data); + if (ret != 0) + return ret; + + if (checksum_data.length < PAC_SIGNATURE_DATA_LENGTH) + return KRB5_BAD_MSIZE; + + p = (krb5_octet *)checksum_data.data; + checksum.checksum_type = load_32_le(p); + checksum.length = checksum_data.length - PAC_SIGNATURE_DATA_LENGTH; + checksum.contents = p + PAC_SIGNATURE_DATA_LENGTH; + + pac_data.length = pac->data.length; + pac_data.data = malloc(pac->data.length); + if (pac_data.data == NULL) + return ENOMEM; + + memcpy(pac_data.data, pac->data.data, pac->data.length); + + /* Zero out both checksum buffers */ + ret = k5_pac_zero_signature(context, pac, PAC_SERVER_CHECKSUM, &pac_data); + if (ret != 0) { + free(pac_data.data); + return ret; + } + + ret = k5_pac_zero_signature(context, pac, PAC_PRIVSVR_CHECKSUM, &pac_data); + if (ret != 0) { + free(pac_data.data); + return ret; + } + + ret = krb5_c_verify_checksum(context, server, KRB5_KEYUSAGE_APP_DATA_CKSUM, + &pac_data, &checksum, &valid); + if (ret != 0) { + free(pac_data.data); + return ret; + } + + if (valid == FALSE) + ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; + + return ret; +} + +static krb5_error_code +k5_pac_verify_kdc_checksum(krb5_context context, + const krb5_pac pac, + const krb5_keyblock *privsvr) +{ + krb5_error_code ret; + krb5_data server_checksum, privsvr_checksum; + krb5_checksum checksum; + krb5_boolean valid; + krb5_octet *p; + + ret = k5_pac_locate_buffer(context, pac, PAC_PRIVSVR_CHECKSUM, &privsvr_checksum); + if (ret != 0) + return ret; + + if (privsvr_checksum.length < PAC_SIGNATURE_DATA_LENGTH) + return KRB5_BAD_MSIZE; + + ret = k5_pac_locate_buffer(context, pac, PAC_SERVER_CHECKSUM, &server_checksum); + if (ret != 0) + return ret; + + if (server_checksum.length < PAC_SIGNATURE_DATA_LENGTH) + return KRB5_BAD_MSIZE; + + p = (krb5_octet *)privsvr_checksum.data; + checksum.checksum_type = load_32_le(p); + checksum.length = privsvr_checksum.length - PAC_SIGNATURE_DATA_LENGTH; + checksum.contents = p + PAC_SIGNATURE_DATA_LENGTH; + + server_checksum.data += PAC_SIGNATURE_DATA_LENGTH; + server_checksum.length -= PAC_SIGNATURE_DATA_LENGTH; + + ret = krb5_c_verify_checksum(context, privsvr, KRB5_KEYUSAGE_APP_DATA_CKSUM, + &server_checksum, &checksum, &valid); + if (ret != 0) + return ret; + + if (valid == FALSE) + ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; + + return ret; +} + +krb5_error_code KRB5_CALLCONV +krb5_pac_verify(krb5_context context, + const krb5_pac pac, + krb5_timestamp authtime, + krb5_const_principal principal, + const krb5_keyblock *server, + const krb5_keyblock *privsvr) +{ + krb5_error_code ret; + + if (server == NULL) + return EINVAL; + + ret = k5_pac_verify_server_checksum(context, pac, server); + if (ret != 0) + return ret; + + if (privsvr != NULL) { + ret = k5_pac_verify_kdc_checksum(context, pac, privsvr); + if (ret != 0) + return ret; + } + + if (principal != NULL) { + ret = k5_pac_validate_client(context, pac, authtime, principal); + if (ret != 0) + return ret; + } + + return 0; +} + +static krb5_error_code +k5_insert_client_info(krb5_context context, + krb5_pac pac, + krb5_timestamp authtime, + krb5_const_principal principal) +{ + krb5_error_code ret; + krb5_data client_info; + char *princ_name_utf8 = NULL; + unsigned char *princ_name_ucs2 = NULL, *p; + size_t princ_name_ucs2_len = 0; + krb5_ui_8 nt_authtime; + + /* If we already have a CLIENT_INFO buffer, then just validate it */ + if (k5_pac_locate_buffer(context, pac, PAC_CLIENT_INFO, &client_info) == 0) { + return k5_pac_validate_client(context, pac, authtime, principal); + } + + ret = krb5_unparse_name_flags(context, principal, + KRB5_PRINCIPAL_UNPARSE_NO_REALM, &princ_name_utf8); + if (ret != 0) + goto cleanup; + + ret = krb5int_utf8s_to_ucs2les(princ_name_utf8, + &princ_name_ucs2, + &princ_name_ucs2_len); + if (ret != 0) + goto cleanup; + + client_info.length = PAC_CLIENT_INFO_LENGTH + princ_name_ucs2_len; + client_info.data = NULL; + + ret = k5_pac_add_buffer(context, pac, PAC_CLIENT_INFO, &client_info, TRUE, &client_info); + if (ret != 0) + goto cleanup; + + p = (unsigned char *)client_info.data; + + /* copy in authtime converted to a 64-bit NT time */ + k5_seconds_since_1970_to_time(authtime, &nt_authtime); + store_64_le(nt_authtime, p); + p += 8; + + /* copy in number of UCS-2 characters in principal name */ + store_16_le(princ_name_ucs2_len, p); + p += 2; + + /* copy in principal name */ + memcpy(p, princ_name_ucs2, princ_name_ucs2_len); + +cleanup: + if (princ_name_utf8 != NULL) + free(princ_name_utf8); + if (princ_name_ucs2 != NULL) + free(princ_name_ucs2); + + return ret; +} + +static krb5_error_code +k5_insert_checksum(krb5_context context, + krb5_pac pac, + krb5_ui_4 type, + const krb5_keyblock *key, + krb5_cksumtype *cksumtype) +{ + krb5_error_code ret; + size_t len; + krb5_data cksumdata; + + ret = krb5int_c_mandatory_cksumtype(context, key->enctype, cksumtype); + if (ret != 0) + return ret; + + ret = krb5_c_checksum_length(context, *cksumtype, &len); + if (ret != 0) + return ret; + + ret = k5_pac_locate_buffer(context, pac, type, &cksumdata); + if (ret == 0) { + /* If we're resigning PAC, make sure we can fit checksum into existing buffer */ + if (cksumdata.length != PAC_SIGNATURE_DATA_LENGTH + len) + return ERANGE; + + memset(cksumdata.data, 0, cksumdata.length); + } else { + /* Add a zero filled buffer */ + cksumdata.length = PAC_SIGNATURE_DATA_LENGTH + len; + cksumdata.data = NULL; + + ret = k5_pac_add_buffer(context, pac, type, &cksumdata, TRUE, &cksumdata); + if (ret != 0) + return ret; + } + + /* Encode checksum type into buffer */ + store_32_le((krb5_ui_4)*cksumtype, (unsigned char *)cksumdata.data); + + return 0; +} + +/* in-place encoding of PAC header */ +static krb5_error_code +k5_pac_encode_header(krb5_context context, krb5_pac pac) +{ + size_t i; + unsigned char *p; + size_t header_len; + + header_len = PACTYPE_LENGTH + (pac->pac->cBuffers * PAC_INFO_BUFFER_LENGTH); + assert(pac->data.length >= header_len); + + p = (unsigned char *)pac->data.data; + + store_32_le(pac->pac->cBuffers, p); + p += 4; + store_32_le(pac->pac->Version, p); + p += 4; + + for (i = 0; i < pac->pac->cBuffers; i++) { + PAC_INFO_BUFFER *buffer = &pac->pac->Buffers[i]; + + store_32_le(buffer->ulType, p); + p += 4; + store_32_le(buffer->cbBufferSize, p); + p += 4; + store_64_le(buffer->Offset, p); + p += 8; + + assert((buffer->Offset % PAC_ALIGNMENT) == 0); + assert(buffer->Offset + buffer->cbBufferSize <= pac->data.length); + assert(buffer->Offset >= header_len); + + if (buffer->Offset % PAC_ALIGNMENT || + buffer->Offset + buffer->cbBufferSize > pac->data.length || + buffer->Offset < header_len) + return ERANGE; + } + + return 0; +} + +krb5_error_code KRB5_CALLCONV +krb5int_pac_sign(krb5_context context, + krb5_pac pac, + krb5_timestamp authtime, + krb5_const_principal principal, + const krb5_keyblock *server_key, + const krb5_keyblock *privsvr_key, + krb5_data *data) +{ + krb5_error_code ret; + krb5_data server_cksum, privsvr_cksum; + krb5_cksumtype server_cksumtype, privsvr_cksumtype; + krb5_crypto_iov iov[2]; + + data->length = 0; + data->data = NULL; + + if (principal != NULL) { + ret = k5_insert_client_info(context, pac, authtime, principal); + if (ret != 0) + return ret; + } + + /* Create zeroed buffers for both checksums */ + ret = k5_insert_checksum(context, pac, PAC_SERVER_CHECKSUM, + server_key, &server_cksumtype); + if (ret != 0) + return ret; + + ret = k5_insert_checksum(context, pac, PAC_PRIVSVR_CHECKSUM, + privsvr_key, &privsvr_cksumtype); + if (ret != 0) + return ret; + + /* Now, encode the PAC header so that the checksums will include it */ + ret = k5_pac_encode_header(context, pac); + if (ret != 0) + return ret; + + /* Generate the server checksum over the entire PAC */ + ret = k5_pac_locate_buffer(context, pac, PAC_SERVER_CHECKSUM, &server_cksum); + if (ret != 0) + return ret; + + assert(server_cksum.length > PAC_SIGNATURE_DATA_LENGTH); + + iov[0].flags = KRB5_CRYPTO_TYPE_DATA; + iov[0].data = pac->data; + + iov[1].flags = KRB5_CRYPTO_TYPE_CHECKSUM; + iov[1].data.data = server_cksum.data + PAC_SIGNATURE_DATA_LENGTH; + iov[1].data.length = server_cksum.length - PAC_SIGNATURE_DATA_LENGTH; + + ret = krb5_c_make_checksum_iov(context, server_cksumtype, + server_key, KRB5_KEYUSAGE_APP_DATA_CKSUM, + iov, sizeof(iov)/sizeof(iov[0])); + if (ret != 0) + return ret; + + /* Generate the privsvr checksum over the server checksum buffer */ + ret = k5_pac_locate_buffer(context, pac, PAC_PRIVSVR_CHECKSUM, &privsvr_cksum); + if (ret != 0) + return ret; + + assert(privsvr_cksum.length > PAC_SIGNATURE_DATA_LENGTH); + + iov[0].flags = KRB5_CRYPTO_TYPE_DATA; + iov[0].data.data = server_cksum.data + PAC_SIGNATURE_DATA_LENGTH; + iov[0].data.length = server_cksum.length - PAC_SIGNATURE_DATA_LENGTH; + + iov[1].flags = KRB5_CRYPTO_TYPE_CHECKSUM; + iov[1].data.data = privsvr_cksum.data + PAC_SIGNATURE_DATA_LENGTH; + iov[1].data.length = privsvr_cksum.length - PAC_SIGNATURE_DATA_LENGTH; + + ret = krb5_c_make_checksum_iov(context, privsvr_cksumtype, + privsvr_key, KRB5_KEYUSAGE_APP_DATA_CKSUM, + iov, sizeof(iov)/sizeof(iov[0])); + if (ret != 0) + return ret; + + data->data = malloc(pac->data.length); + if (data->data == NULL) + return ENOMEM; + + data->length = pac->data.length; + + memcpy(data->data, pac->data.data, pac->data.length); + memset(pac->data.data, 0, PACTYPE_LENGTH + (pac->pac->cBuffers * PAC_INFO_BUFFER_LENGTH)); + + return 0; +} + diff --git a/src/lib/krb5/krb/parse.c b/src/lib/krb5/krb/parse.c index c6b1f6ebe6..5c705490d3 100644 --- a/src/lib/krb5/krb/parse.c +++ b/src/lib/krb5/krb/parse.c @@ -1,7 +1,7 @@ /* * lib/krb5/krb/parse.c * - * Copyright 1990,1991 by the Massachusetts Institute of Technology. + * Copyright 1990,1991,2008 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may @@ -59,17 +59,17 @@ #define FCOMPNUM 10 - /* * May the fleas of a thousand camels infest the ISO, they who think * that arbitrarily large multi-component names are a Good Thing..... */ -krb5_error_code KRB5_CALLCONV -krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincipal) +static krb5_error_code +k5_parse_name(krb5_context context, const char *name, + int flags, krb5_principal *nprincipal) { register const char *cp; register char *q; - register int i,c,size; + register int i,c,size; int components = 0; const char *parsed_realm = NULL; int fcompsize[FCOMPNUM]; @@ -79,24 +79,28 @@ krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincip char *tmpdata; krb5_principal principal; krb5_error_code retval; - + unsigned int enterprise = (flags & KRB5_PRINCIPAL_PARSE_ENTERPRISE); + int first_at; + /* * Pass 1. Find out how many components there are to the name, - * and get string sizes for the first FCOMPNUM components. + * and get string sizes for the first FCOMPNUM components. For + * enterprise principal names (UPNs), there is only a single + * component. */ size = 0; - for (i=0,cp = name; (c = *cp); cp++) { + for (i=0,cp = name, first_at = 1; (c = *cp); cp++) { if (c == QUOTECHAR) { cp++; if (!(c = *cp)) /* - * QUOTECHAR can't be at the last - * character of the name! - */ + * QUOTECHAR can't be at the last + * character of the name! + */ return(KRB5_PARSE_MALFORMED); size++; continue; - } else if (c == COMPONENT_SEP) { + } else if (c == COMPONENT_SEP && !enterprise) { if (parsed_realm) /* * Shouldn't see a component separator @@ -108,22 +112,26 @@ krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincip } size = 0; i++; - } else if (c == REALM_SEP) { + } else if (c == REALM_SEP && (!enterprise || !first_at)) { if (parsed_realm) /* * Multiple realm separaters * not allowed; zero-length realms are. */ return(KRB5_PARSE_MALFORMED); - parsed_realm = cp+1; + parsed_realm = cp + 1; if (i < FCOMPNUM) { fcompsize[i] = size; } size = 0; - } else + } else { + if (c == REALM_SEP && enterprise && first_at) + first_at = 0; + size++; + } } - if (parsed_realm) + if (parsed_realm != NULL) realmsize = size; else if (i < FCOMPNUM) fcompsize[i] = size; @@ -133,20 +141,30 @@ krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincip * component pieces */ principal = (krb5_principal)malloc(sizeof(krb5_principal_data)); - if (!principal) { - return(ENOMEM); + if (principal == NULL) { + return(ENOMEM); } principal->data = (krb5_data *) malloc(sizeof(krb5_data) * components); - if (!principal->data) { - free((char *)principal); + if (principal->data == NULL) { + krb5_xfree((char *)principal); return ENOMEM; } principal->length = components; + /* - * If a realm was not found, then use the defualt realm.... + * If a realm was not found, then use the default realm, unless + * KRB5_PRINCIPAL_PARSE_NO_REALM was specified in which case the + * realm will be empty. */ if (!parsed_realm) { - if (!default_realm) { + if (flags & KRB5_PRINCIPAL_PARSE_REQUIRE_REALM) { + krb5_set_error_message(context, KRB5_PARSE_MALFORMED, + "Principal %s is missing required realm", name); + krb5_xfree(principal->data); + krb5_xfree(principal); + return KRB5_PARSE_MALFORMED; + } + if (!default_realm && (flags & KRB5_PRINCIPAL_PARSE_NO_REALM) == 0) { retval = krb5_get_default_realm(context, &default_realm); if (retval) { krb5_xfree(principal->data); @@ -156,7 +174,14 @@ krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincip default_realm_size = strlen(default_realm); } realmsize = default_realm_size; + } else if (flags & KRB5_PRINCIPAL_PARSE_NO_REALM) { + krb5_set_error_message(context, KRB5_PARSE_MALFORMED, + "Principal %s has realm present", name); + krb5_xfree(principal->data); + krb5_xfree(principal); + return KRB5_PARSE_MALFORMED; } + /* * Pass 2. Happens only if there were more than FCOMPNUM * component; if this happens, someone should be shot @@ -208,7 +233,7 @@ krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincip /* * Now, we need to allocate the space for the strings themselves..... */ - tmpdata = malloc(realmsize+1); + tmpdata = malloc(realmsize + 1); if (tmpdata == 0) { krb5_xfree(principal->data); krb5_xfree(principal); @@ -220,7 +245,7 @@ krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincip for (i=0; i < components; i++) { char *tmpdata2 = malloc(krb5_princ_component(context, principal, i)->length + 1); - if (!tmpdata2) { + if (tmpdata2 == NULL) { for (i--; i >= 0; i--) krb5_xfree(krb5_princ_component(context, principal, i)->data); krb5_xfree(krb5_princ_realm(context, principal)->data); @@ -239,7 +264,7 @@ krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincip * allocated. */ q = krb5_princ_component(context, principal, 0)->data; - for (i=0,cp = name; (c = *cp); cp++) { + for (i=0,cp = name, first_at = 1; (c = *cp); cp++) { if (c == QUOTECHAR) { cp++; switch (c = *cp) { @@ -257,29 +282,57 @@ krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincip break; default: *q++ = c; + break; } - } else if ((c == COMPONENT_SEP) || (c == REALM_SEP)) { + } else if (c == COMPONENT_SEP && !enterprise) { + i++; + *q++ = '\0'; + q = krb5_princ_component(context, principal, i)->data; + } else if (c == REALM_SEP && (!enterprise || !first_at)) { i++; *q++ = '\0'; - if (c == COMPONENT_SEP) - q = krb5_princ_component(context, principal, i)->data; - else - q = krb5_princ_realm(context, principal)->data; - } else + q = krb5_princ_realm(context, principal)->data; + } else { + if (c == REALM_SEP && enterprise && first_at) + first_at = 0; + *q++ = c; + } } *q++ = '\0'; - if (!parsed_realm) - strlcpy(krb5_princ_realm(context, principal)->data, default_realm, realmsize + 1); + if (!parsed_realm) { + if (flags & KRB5_PRINCIPAL_PARSE_NO_REALM) + (krb5_princ_realm(context, principal)->data)[0] = '\0'; + else + strlcpy(krb5_princ_realm(context, principal)->data, default_realm, realmsize+1); + } /* * Alright, we're done. Now stuff a pointer to this monstrosity * into the return variable, and let's get out of here. */ - krb5_princ_type(context, principal) = KRB5_NT_PRINCIPAL; + if (enterprise) + krb5_princ_type(context, principal) = KRB5_NT_ENTERPRISE_PRINCIPAL; + else + krb5_princ_type(context, principal) = KRB5_NT_PRINCIPAL; principal->magic = KV5M_PRINCIPAL; principal->realm.magic = KV5M_DATA; *nprincipal = principal; - krb5_xfree(default_realm); + if (default_realm != NULL) + krb5_xfree(default_realm); + return(0); } + +krb5_error_code KRB5_CALLCONV +krb5_parse_name(krb5_context context, const char *name, krb5_principal *nprincipal) +{ + return k5_parse_name(context, name, 0, nprincipal); +} + +krb5_error_code KRB5_CALLCONV +krb5_parse_name_flags(krb5_context context, const char *name, + int flags, krb5_principal *nprincipal) +{ + return k5_parse_name(context, name, flags, nprincipal); +} diff --git a/src/lib/krb5/krb/princ_comp.c b/src/lib/krb5/krb/princ_comp.c index 6e033ad918..46e00e48d1 100644 --- a/src/lib/krb5/krb/princ_comp.c +++ b/src/lib/krb5/krb/princ_comp.c @@ -29,37 +29,116 @@ */ #include "k5-int.h" +#include "k5-unicode.h" + +static krb5_boolean +realm_compare_flags(krb5_context context, + krb5_const_principal princ1, + krb5_const_principal princ2, + int flags) +{ + const krb5_data *realm1 = krb5_princ_realm(context, princ1); + const krb5_data *realm2 = krb5_princ_realm(context, princ2); + + if (realm1->length != realm2->length) + return FALSE; + + return (flags & KRB5_PRINCIPAL_COMPARE_CASEFOLD) ? + (strncasecmp(realm1->data, realm2->data, realm2->length) == 0) : + (memcmp(realm1->data, realm2->data, realm2->length) == 0); +} krb5_boolean KRB5_CALLCONV krb5_realm_compare(krb5_context context, krb5_const_principal princ1, krb5_const_principal princ2) { - if (!data_eq(*krb5_princ_realm(context, princ1), - *krb5_princ_realm(context, princ2))) - return FALSE; + return realm_compare_flags(context, princ1, princ2, 0); +} - return TRUE; +static krb5_error_code +upn_to_principal(krb5_context context, + krb5_const_principal princ, + krb5_principal *upn) +{ + char *unparsed_name; + krb5_error_code code; + + code = krb5_unparse_name_flags(context, princ, + KRB5_PRINCIPAL_UNPARSE_NO_REALM, + &unparsed_name); + if (code) { + *upn = NULL; + return code; + } + + code = krb5_parse_name(context, unparsed_name, upn); + + free(unparsed_name); + + return code; } krb5_boolean KRB5_CALLCONV -krb5_principal_compare(krb5_context context, krb5_const_principal princ1, krb5_const_principal princ2) +krb5_principal_compare_flags(krb5_context context, + krb5_const_principal princ1, + krb5_const_principal princ2, + int flags) { register int i; krb5_int32 nelem; + unsigned int utf8 = (flags & KRB5_PRINCIPAL_COMPARE_UTF8) != 0; + unsigned int casefold = (flags & KRB5_PRINCIPAL_COMPARE_CASEFOLD) != 0; + krb5_principal upn1 = NULL; + krb5_principal upn2 = NULL; + krb5_boolean ret = FALSE; + + if (flags & KRB5_PRINCIPAL_COMPARE_ENTERPRISE) { + /* Treat UPNs as if they were real principals */ + if (krb5_princ_type(context, princ1) == KRB5_NT_ENTERPRISE_PRINCIPAL) { + if (upn_to_principal(context, princ1, &upn1) == 0) + princ1 = upn1; + } + if (krb5_princ_type(context, princ2) == KRB5_NT_ENTERPRISE_PRINCIPAL) { + if (upn_to_principal(context, princ2, &upn2) == 0) + princ2 = upn2; + } + } nelem = krb5_princ_size(context, princ1); if (nelem != krb5_princ_size(context, princ2)) - return FALSE; + goto out; - if (! krb5_realm_compare(context, princ1, princ2)) - return FALSE; + if ((flags & KRB5_PRINCIPAL_COMPARE_IGNORE_REALM) == 0 && + !realm_compare_flags(context, princ1, princ2, flags)) + goto out; for (i = 0; i < (int) nelem; i++) { register const krb5_data *p1 = krb5_princ_component(context, princ1, i); register const krb5_data *p2 = krb5_princ_component(context, princ2, i); - if (!data_eq(*p1, *p2)) - return FALSE; + int cmp; + + if (casefold) { + if (utf8) + cmp = krb5int_utf8_normcmp(p1, p2, KRB5_UTF8_CASEFOLD); + else + cmp = p1->length == p2->length ? + strncasecmp(p1->data, p2->data, p2->length) : + p1->length - p2->length; + } else + cmp = !data_eq(*p1, *p2); + + if (cmp != 0) + goto out; } - return TRUE; + + ret = TRUE; + +out: + if (upn1 != NULL) + krb5_free_principal(context, upn1); + if (upn2 != NULL) + krb5_free_principal(context, upn2); + + return ret; } krb5_boolean KRB5_CALLCONV krb5_is_referral_realm(const krb5_data *r) @@ -81,3 +160,20 @@ krb5_boolean KRB5_CALLCONV krb5_is_referral_realm(const krb5_data *r) else return FALSE; } + +krb5_boolean KRB5_CALLCONV +krb5_principal_compare(krb5_context context, + krb5_const_principal princ1, + krb5_const_principal princ2) +{ + return krb5_principal_compare_flags(context, princ1, princ2, 0); +} + +krb5_boolean KRB5_CALLCONV +krb5_principal_compare_any_realm(krb5_context context, + krb5_const_principal princ1, + krb5_const_principal princ2) +{ + return krb5_principal_compare_flags(context, princ1, princ2, KRB5_PRINCIPAL_COMPARE_IGNORE_REALM); +} + diff --git a/src/lib/krb5/krb/rd_priv.c b/src/lib/krb5/krb/rd_priv.c index 66cee85381..618726efe2 100644 --- a/src/lib/krb5/krb/rd_priv.c +++ b/src/lib/krb5/krb/rd_priv.c @@ -265,7 +265,9 @@ krb5_rd_priv(krb5_context context, krb5_auth_context auth_context, error:; krb5_xfree(outbuf->data); - return retval; + outbuf->length = 0; + outbuf->data = NULL; + return retval; } diff --git a/src/lib/krb5/krb/rd_rep.c b/src/lib/krb5/krb/rd_rep.c index 901de4338a..1e6e0e1e82 100644 --- a/src/lib/krb5/krb/rd_rep.c +++ b/src/lib/krb5/krb/rd_rep.c @@ -26,6 +26,33 @@ * * krb5_rd_rep() */ +/* + * Copyright (c) 2006-2008, Novell, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The copyright holder's name is not used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ #include "k5-int.h" #include "auth_con.h" @@ -102,6 +129,8 @@ krb5_rd_rep(krb5_context context, krb5_auth_context auth_context, krb5_free_keyblock(context, auth_context->send_subkey); auth_context->send_subkey = NULL; } + /* not used for anything yet */ + auth_context->negotiated_etype = (*repl)->subkey->enctype; } /* Get remote sequence number */ @@ -114,3 +143,60 @@ clean_scratch: free(scratch.data); return retval; } + +krb5_error_code KRB5_CALLCONV +krb5_rd_rep_dce(krb5_context context, krb5_auth_context auth_context, + const krb5_data *inbuf, krb5_ui_4 *nonce) +{ + krb5_error_code retval; + krb5_ap_rep * reply; + krb5_data scratch; + krb5_ap_rep_enc_part *repl; + + if (!krb5_is_ap_rep(inbuf)) + return KRB5KRB_AP_ERR_MSG_TYPE; + + /* decode it */ + + if ((retval = decode_krb5_ap_rep(inbuf, &reply))) + return retval; + + /* put together an eblock for this encryption */ + + scratch.length = reply->enc_part.ciphertext.length; + if (!(scratch.data = malloc(scratch.length))) { + krb5_free_ap_rep(context, reply); + return(ENOMEM); + } + + if ((retval = krb5_c_decrypt(context, auth_context->keyblock, + KRB5_KEYUSAGE_AP_REP_ENCPART, 0, + &reply->enc_part, &scratch))) + goto clean_scratch; + + /* now decode the decrypted stuff */ + retval = decode_krb5_ap_rep_enc_part(&scratch, &repl); + if (retval) + goto clean_scratch; + + *nonce = repl->seq_number; + if (*nonce != auth_context->local_seq_number) { + retval = KRB5_MUTUAL_FAILED; + goto clean_scratch; + } + + /* Must be NULL to prevent echoing for client AP-REP */ + if (repl->subkey != NULL) { + retval = KRB5_MUTUAL_FAILED; + goto clean_scratch; + } + +clean_scratch: + memset(scratch.data, 0, scratch.length); + + if (repl != NULL) + krb5_free_ap_rep_enc_part(context, repl); + krb5_free_ap_rep(context, reply); + free(scratch.data); + return retval; +} diff --git a/src/lib/krb5/krb/rd_req.c b/src/lib/krb5/krb/rd_req.c index 6a479496fa..5848aa776f 100644 --- a/src/lib/krb5/krb/rd_req.c +++ b/src/lib/krb5/krb/rd_req.c @@ -77,19 +77,6 @@ krb5_rd_req(krb5_context context, krb5_auth_context *auth_context, *auth_context = new_auth_context; } - if (!server) { - server = request->ticket->server; - } - /* Get an rcache if necessary. */ - if (((*auth_context)->rcache == NULL) - && ((*auth_context)->auth_context_flags & KRB5_AUTH_CONTEXT_DO_TIME) - && server) { - if ((retval = krb5_get_server_rcache(context, - krb5_princ_component(context, - server,0), - &(*auth_context)->rcache))) - goto cleanup_auth_context; - } #ifndef LEAN_CLIENT /* Get a keytab if necessary. */ diff --git a/src/lib/krb5/krb/rd_req_dec.c b/src/lib/krb5/krb/rd_req_dec.c index b81266d264..bbf7ed6a72 100644 --- a/src/lib/krb5/krb/rd_req_dec.c +++ b/src/lib/krb5/krb/rd_req_dec.c @@ -62,6 +62,19 @@ static krb5_error_code decrypt_authenticator (krb5_context, const krb5_ap_req *, krb5_authenticator **, int); +static krb5_error_code +decode_etype_list(krb5_context context, + const krb5_authenticator *authp, + krb5_enctype **desired_etypes, + int *desired_etypes_len); +static krb5_error_code +negotiate_etype(krb5_context context, + const krb5_enctype *desired_etypes, + int desired_etypes_len, + int mandatory_etypes_index, + const krb5_enctype *permitted_etypes, + int permitted_etypes_len, + krb5_enctype *negotiated_etype); krb5_error_code krb5int_check_clockskew(krb5_context context, krb5_timestamp date) @@ -79,27 +92,83 @@ krb5int_check_clockskew(krb5_context context, krb5_timestamp date) static krb5_error_code krb5_rd_req_decrypt_tkt_part(krb5_context context, const krb5_ap_req *req, - krb5_keytab keytab) + krb5_const_principal server, krb5_keytab keytab) { krb5_error_code retval; - krb5_enctype enctype; krb5_keytab_entry ktent; - enctype = req->ticket->enc_part.enctype; + retval = KRB5_KT_NOTFOUND; #ifndef LEAN_CLIENT - if ((retval = krb5_kt_get_entry(context, keytab, req->ticket->server, - req->ticket->enc_part.kvno, - enctype, &ktent))) - return retval; + if (server != NULL || keytab->ops->start_seq_get == NULL) { + retval = krb5_kt_get_entry(context, keytab, + server != NULL ? server : req->ticket->server, + req->ticket->enc_part.kvno, + req->ticket->enc_part.enctype, &ktent); + if (retval == 0) { + retval = krb5_decrypt_tkt_part(context, &ktent.key, req->ticket); + + (void) krb5_free_keytab_entry_contents(context, &ktent); + } + } else { + krb5_error_code code; + krb5_kt_cursor cursor; + + retval = krb5_kt_start_seq_get(context, keytab, &cursor); + if (retval != 0) + goto map_error; + + while ((code = krb5_kt_next_entry(context, keytab, + &ktent, &cursor)) == 0) { + if (ktent.key.enctype != req->ticket->enc_part.enctype) + continue; + + retval = krb5_decrypt_tkt_part(context, &ktent.key, + req->ticket); + + if (retval == 0) { + krb5_principal tmp; + + /* + * We overwrite ticket->server to be the principal + * that we match in the keytab. The reason for doing + * this is that GSS-API and other consumers look at + * that principal to make authorization decisions + * about whether the appropriate server is contacted. + * It might be cleaner to create a new API and store + * the server in the auth_context, but doing so would + * probably miss existing uses of the server. Instead, + * perhaps an API should be created to retrieve the + * server as it appeared in the ticket. + */ + retval = krb5_copy_principal(context, ktent.principal, &tmp); + if (retval == 0) { + krb5_free_principal(context, req->ticket->server); + req->ticket->server = tmp; + } + (void) krb5_free_keytab_entry_contents(context, &ktent); + break; + } + (void) krb5_free_keytab_entry_contents(context, &ktent); + } + + code = krb5_kt_end_seq_get(context, keytab, &cursor); + if (code != 0) + retval = code; + } #endif /* LEAN_CLIENT */ - retval = krb5_decrypt_tkt_part(context, &ktent.key, req->ticket); - /* Upon error, Free keytab entry first, then return */ +map_error: + switch (retval) { + case KRB5_KT_KVNONOTFOUND: + case KRB5_KT_NOTFOUND: + case KRB5KRB_AP_ERR_BAD_INTEGRITY: + retval = KRB5KRB_AP_WRONG_PRINC; + break; + default: + break; + } -#ifndef LEAN_CLIENT - (void) krb5_kt_free_entry(context, &ktent); -#endif /* LEAN_CLIENT */ return retval; } @@ -134,8 +203,13 @@ krb5_rd_req_decoded_opt(krb5_context context, krb5_auth_context *auth_context, krb5_ticket **ticket, int check_valid_flag) { krb5_error_code retval = 0; - krb5_principal_data princ_data; - + krb5_principal_data princ_data; + krb5_enctype *desired_etypes = NULL; + int desired_etypes_len = 0; + int rfc4537_etypes_len = 0; + krb5_enctype *permitted_etypes = NULL; + int permitted_etypes_len = 0; + req->ticket->enc_part2 = NULL; if (server && krb5_is_referral_realm(&server->realm)) { char *realm; @@ -147,18 +221,7 @@ krb5_rd_req_decoded_opt(krb5_context context, krb5_auth_context *auth_context, princ_data.realm.data = realm; princ_data.realm.length = strlen(realm); } - if (server && !krb5_principal_compare(context, server, req->ticket->server)) { - char *found_name = 0, *wanted_name = 0; - if (krb5_unparse_name(context, server, &wanted_name) == 0 - && krb5_unparse_name(context, req->ticket->server, &found_name) == 0) - krb5_set_error_message(context, KRB5KRB_AP_WRONG_PRINC, - "Wrong principal in request (found %s, wanted %s)", - found_name, wanted_name); - krb5_free_unparsed_name(context, wanted_name); - krb5_free_unparsed_name(context, found_name); - retval = KRB5KRB_AP_WRONG_PRINC; - goto cleanup; - } + /* if (req->ap_options & AP_OPTS_USE_SESSION_KEY) do we need special processing here ? */ @@ -171,7 +234,7 @@ krb5_rd_req_decoded_opt(krb5_context context, krb5_auth_context *auth_context, krb5_free_keyblock(context, (*auth_context)->keyblock); (*auth_context)->keyblock = NULL; } else { - if ((retval = krb5_rd_req_decrypt_tkt_part(context, req, keytab))) + if ((retval = krb5_rd_req_decrypt_tkt_part(context, req, server, keytab))) goto cleanup; } @@ -197,6 +260,19 @@ krb5_rd_req_decoded_opt(krb5_context context, krb5_auth_context *auth_context, goto cleanup; } + if (!server) { + server = req->ticket->server; + } + /* Get an rcache if necessary. */ + if (((*auth_context)->rcache == NULL) + && ((*auth_context)->auth_context_flags & KRB5_AUTH_CONTEXT_DO_TIME) + && server) { + if ((retval = krb5_get_server_rcache(context, + krb5_princ_component(context, + server,0), + &(*auth_context)->rcache))) + goto cleanup; + } /* okay, now check cross-realm policy */ #if defined(_SINGLE_HOP_ONLY) @@ -296,126 +372,86 @@ krb5_rd_req_decoded_opt(krb5_context context, krb5_auth_context *auth_context, } } - /* check if the various etypes are permitted */ - - if ((*auth_context)->auth_context_flags & KRB5_AUTH_CONTEXT_PERMIT_ALL) { - /* no etype check needed */; - } else if ((*auth_context)->permitted_etypes == NULL) { - int etype; - size_t size_etype_enc = 3 * sizeof(krb5_enctype); /* upto three types */ - size_t size_etype_bool = 3 * sizeof(krb5_boolean); - krb5_etypes_permitted etypes; - memset(&etypes, 0, sizeof etypes); - - etypes.etype = (krb5_enctype*) malloc(size_etype_enc); - if (etypes.etype == NULL) { - retval = ENOMEM; - goto cleanup; - } - etypes.etype_ok = (krb5_boolean*) malloc(size_etype_bool); - if (etypes.etype_ok == NULL) { - retval = ENOMEM; - free(etypes.etype); - goto cleanup; - } - memset(etypes.etype, 0, size_etype_enc); - memset(etypes.etype_ok, 0, size_etype_bool); + /* read RFC 4537 etype list from sender */ + retval = decode_etype_list(context, + (*auth_context)->authentp, + &desired_etypes, + &rfc4537_etypes_len); + if (retval != 0) + goto cleanup; - etypes.etype[etypes.etype_count++] = req->ticket->enc_part.enctype; - etypes.etype[etypes.etype_count++] = req->ticket->enc_part2->session->enctype; - if ((*auth_context)->authentp->subkey) { - etypes.etype[etypes.etype_count++] = (*auth_context)->authentp->subkey->enctype; - } + if (desired_etypes == NULL) + desired_etypes = (krb5_enctype *)calloc(4, sizeof(krb5_enctype)); + else + desired_etypes = (krb5_enctype *)realloc(desired_etypes, + (rfc4537_etypes_len + 4) * + sizeof(krb5_enctype)); + if (desired_etypes == NULL) { + retval = ENOMEM; + goto cleanup; + } - retval = krb5_is_permitted_enctype_ext(context, &etypes); + desired_etypes_len = rfc4537_etypes_len; -#if 0 - /* check against the default set */ - if ((!krb5_is_permitted_enctype(context, - etype = req->ticket->enc_part.enctype)) || - (!krb5_is_permitted_enctype(context, - etype = req->ticket->enc_part2->session->enctype)) || - (((*auth_context)->authentp->subkey) && - !krb5_is_permitted_enctype(context, - etype = (*auth_context)->authentp->subkey->enctype))) { -#endif - if (retval == 0 /* all etypes are not permitted */ || - (!etypes.etype_ok[0] || !etypes.etype_ok[1] || - (((*auth_context)->authentp->subkey) && !etypes.etype_ok[etypes.etype_count-1]))) { - char enctype_name[30]; - retval = KRB5_NOPERM_ETYPE; - - if (!etypes.etype_ok[0]) { - etype = etypes.etype[1]; - } else if (!etypes.etype_ok[1]) { - etype = etypes.etype[1]; - } else { - etype = etypes.etype[2]; - } - free(etypes.etype); - free(etypes.etype_ok); - - if (krb5_enctype_to_string(etype, enctype_name, sizeof(enctype_name)) == 0) - krb5_set_error_message(context, retval, - "Encryption type %s not permitted", - enctype_name); - goto cleanup; - } - free(etypes.etype); - free(etypes.etype_ok); - } else { - /* check against the set in the auth_context */ - int i; + /* + * RFC 4537: + * + * If the EtypeList is present and the server prefers an enctype from + * the client's enctype list over that of the AP-REQ authenticator + * subkey (if that is present) or the service ticket session key, the + * server MUST create a subkey using that enctype. This negotiated + * subkey is sent in the subkey field of AP-REP message, and it is then + * used as the protocol key or base key [RFC3961] for subsequent + * communication. + * + * If the enctype of the ticket session key is included in the enctype + * list sent by the client, it SHOULD be the last on the list; + * otherwise, this enctype MUST NOT be negotiated if it was not included + * in the list. + * + * The second paragraph does appear to contradict the first with respect + * to whether it is legal to negotiate the ticket session key type if it + * is absent in the EtypeList. A literal reading suggests that we can use + * the AP-REQ subkey enctype. Also a client has no way of distinguishing + * a server that does not RFC 4537 from one that has chosen the same + * enctype as the ticket session key for the acceptor subkey, surely. + */ - for (i=0; (*auth_context)->permitted_etypes[i]; i++) - if ((*auth_context)->permitted_etypes[i] == - req->ticket->enc_part.enctype) - break; - if (!(*auth_context)->permitted_etypes[i]) { - char enctype_name[30]; - retval = KRB5_NOPERM_ETYPE; - if (krb5_enctype_to_string(req->ticket->enc_part.enctype, - enctype_name, sizeof(enctype_name)) == 0) - krb5_set_error_message(context, retval, - "Encryption type %s not permitted", - enctype_name); - goto cleanup; - } - - for (i=0; (*auth_context)->permitted_etypes[i]; i++) - if ((*auth_context)->permitted_etypes[i] == - req->ticket->enc_part2->session->enctype) - break; - if (!(*auth_context)->permitted_etypes[i]) { - char enctype_name[30]; - retval = KRB5_NOPERM_ETYPE; - if (krb5_enctype_to_string(req->ticket->enc_part2->session->enctype, - enctype_name, sizeof(enctype_name)) == 0) - krb5_set_error_message(context, retval, - "Encryption type %s not permitted", - enctype_name); - goto cleanup; - } - - if ((*auth_context)->authentp->subkey) { - for (i=0; (*auth_context)->permitted_etypes[i]; i++) - if ((*auth_context)->permitted_etypes[i] == - (*auth_context)->authentp->subkey->enctype) - break; - if (!(*auth_context)->permitted_etypes[i]) { - char enctype_name[30]; - retval = KRB5_NOPERM_ETYPE; - if (krb5_enctype_to_string((*auth_context)->authentp->subkey->enctype, - enctype_name, - sizeof(enctype_name)) == 0) - krb5_set_error_message(context, retval, - "Encryption type %s not permitted", - enctype_name); + if ((*auth_context)->authentp->subkey != NULL) { + desired_etypes[desired_etypes_len++] = (*auth_context)->authentp->subkey->enctype; + } + desired_etypes[desired_etypes_len++] = req->ticket->enc_part2->session->enctype; + desired_etypes[desired_etypes_len++] = req->ticket->enc_part.enctype; + desired_etypes[desired_etypes_len] = ENCTYPE_NULL; + + if (((*auth_context)->auth_context_flags & KRB5_AUTH_CONTEXT_PERMIT_ALL) == 0) { + if ((*auth_context)->permitted_etypes != NULL) { + permitted_etypes = (*auth_context)->permitted_etypes; + } else { + retval = krb5_get_permitted_enctypes(context, &permitted_etypes); + if (retval != 0) goto cleanup; - } } + for (permitted_etypes_len = 0; + permitted_etypes[permitted_etypes_len] != ENCTYPE_NULL; + permitted_etypes_len++) + ; + } else { + permitted_etypes = NULL; + permitted_etypes_len = 0; } + /* check if the various etypes are permitted */ + retval = negotiate_etype(context, + desired_etypes, desired_etypes_len, + rfc4537_etypes_len, + permitted_etypes, permitted_etypes_len, + &(*auth_context)->negotiated_etype); + if (retval != 0) + goto cleanup; + + assert((*auth_context)->negotiated_etype != ENCTYPE_NULL); + (*auth_context)->remote_seq_number = (*auth_context)->authentp->seq_number; if ((*auth_context)->authentp->subkey) { if ((retval = krb5_copy_keyblock(context, @@ -454,11 +490,22 @@ krb5_rd_req_decoded_opt(krb5_context context, krb5_auth_context *auth_context, if (ticket) if ((retval = krb5_copy_ticket(context, req->ticket, ticket))) goto cleanup; - if (ap_req_options) - *ap_req_options = req->ap_options; + if (ap_req_options) { + *ap_req_options = req->ap_options & AP_OPTS_WIRE_MASK; + if (rfc4537_etypes_len != 0) + *ap_req_options |= AP_OPTS_ETYPE_NEGOTIATION; + if ((*auth_context)->negotiated_etype != (*auth_context)->keyblock->enctype) + *ap_req_options |= AP_OPTS_USE_SUBKEY; + } + retval = 0; cleanup: + if (desired_etypes != NULL) + krb5_xfree(desired_etypes); + if (permitted_etypes != NULL && + permitted_etypes != (*auth_context)->permitted_etypes) + krb5_xfree(permitted_etypes); if (server == &princ_data) krb5_free_default_realm(context, princ_data.realm.data); if (retval) { @@ -537,3 +584,129 @@ free(scratch.data);} } #endif +static krb5_error_code +negotiate_etype(krb5_context context, + const krb5_enctype *desired_etypes, + int desired_etypes_len, + int mandatory_etypes_index, + const krb5_enctype *permitted_etypes, + int permitted_etypes_len, + krb5_enctype *negotiated_etype) +{ + int i, j; + + *negotiated_etype = ENCTYPE_NULL; + + /* mandatory segment of desired_etypes must be permitted */ + for (i = mandatory_etypes_index; i < desired_etypes_len; i++) { + krb5_boolean permitted = FALSE; + + for (j = 0; j < permitted_etypes_len; j++) { + if (desired_etypes[i] == permitted_etypes[j]) { + permitted = TRUE; + break; + } + } + + if (permitted == FALSE) { + char enctype_name[30]; + + if (krb5_enctype_to_string(desired_etypes[i], + enctype_name, + sizeof(enctype_name)) == 0) + krb5_set_error_message(context, KRB5_NOPERM_ETYPE, + "Encryption type %s not permitted", + enctype_name); + return KRB5_NOPERM_ETYPE; + } + } + + /* + * permitted_etypes is ordered from most to least preferred; + * find first desired_etype that matches. + */ + for (j = 0; j < permitted_etypes_len; j++) { + for (i = 0; i < desired_etypes_len; i++) { + if (desired_etypes[i] == permitted_etypes[j]) { + *negotiated_etype = permitted_etypes[j]; + return 0; + } + } + } + + /*NOTREACHED*/ + return KRB5_NOPERM_ETYPE; +} + +static krb5_error_code +decode_etype_list(krb5_context context, + const krb5_authenticator *authp, + krb5_enctype **desired_etypes, + int *desired_etypes_len) +{ + krb5_error_code code; + krb5_authdata **ad_if_relevant = NULL; + krb5_authdata *etype_adata = NULL; + krb5_etype_list *etype_list = NULL; + int i, j; + krb5_data data; + + *desired_etypes = NULL; + + if (authp->authorization_data == NULL) + return 0; + + /* + * RFC 4537 says that ETYPE_NEGOTIATION auth data should be wrapped + * in AD_IF_RELEVANT, but we handle the case where it is mandatory. + */ + for (i = 0; authp->authorization_data[i] != NULL; i++) { + switch (authp->authorization_data[i]->ad_type) { + case KRB5_AUTHDATA_IF_RELEVANT: + code = krb5_decode_authdata_container(context, + KRB5_AUTHDATA_IF_RELEVANT, + authp->authorization_data[i], + &ad_if_relevant); + if (code != 0) + continue; + + for (j = 0; ad_if_relevant[j] != NULL; j++) { + if (ad_if_relevant[j]->ad_type == KRB5_AUTHDATA_ETYPE_NEGOTIATION) { + etype_adata = ad_if_relevant[j]; + break; + } + } + if (etype_adata == NULL) { + krb5_free_authdata(context, ad_if_relevant); + ad_if_relevant = NULL; + } + break; + case KRB5_AUTHDATA_ETYPE_NEGOTIATION: + etype_adata = authp->authorization_data[i]; + break; + default: + break; + } + if (etype_adata != NULL) + break; + } + + if (etype_adata == NULL) + return 0; + + data.data = (char *)etype_adata->contents; + data.length = etype_adata->length; + + code = decode_krb5_etype_list(&data, &etype_list); + if (code == 0) { + *desired_etypes = etype_list->etypes; + *desired_etypes_len = etype_list->length; + krb5_xfree(etype_list); + } + + if (ad_if_relevant != NULL) + krb5_free_authdata(context, ad_if_relevant); + + return code; +} + diff --git a/src/lib/krb5/krb/serialize.c b/src/lib/krb5/krb/serialize.c index fc20fb1928..9152dba0a7 100644 --- a/src/lib/krb5/krb/serialize.c +++ b/src/lib/krb5/krb/serialize.c @@ -62,7 +62,8 @@ krb5_register_serializer(krb5_context kcontext, const krb5_ser_entry *entry) kret = 0; /* See if it's already there, if so, we're good to go. */ - if (!(stable = krb5_find_serializer(kcontext, entry->odtype))) { + if (!(stable = (krb5_ser_entry *)krb5_find_serializer(kcontext, + entry->odtype))) { /* * Can't find our type. Create a new entry. */ diff --git a/src/lib/krb5/krb/unparse.c b/src/lib/krb5/krb/unparse.c index e24dccf097..ec0976fb22 100644 --- a/src/lib/krb5/krb/unparse.c +++ b/src/lib/krb5/krb/unparse.c @@ -1,7 +1,7 @@ /* * lib/krb5/krb/unparse.c * - * Copyright 1990 by the Massachusetts Institute of Technology. + * Copyright 1990, 2008 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may @@ -58,33 +58,52 @@ #define COMPONENT_SEP '/' static int -component_length_quoted(const krb5_data *src) +component_length_quoted(const krb5_data *src, int flags) { const char *cp = src->data; int length = src->length; int j; int size = length; - for (j = 0; j < length; j++,cp++) - if (*cp == REALM_SEP || *cp == COMPONENT_SEP || - *cp == '\0' || *cp == '\\' || *cp == '\t' || - *cp == '\n' || *cp == '\b') - size++; + if ((flags & KRB5_PRINCIPAL_UNPARSE_DISPLAY) == 0) { + int no_realm = (flags & KRB5_PRINCIPAL_UNPARSE_NO_REALM) && + !(flags & KRB5_PRINCIPAL_UNPARSE_SHORT); + + for (j = 0; j < length; j++,cp++) + if ((!no_realm && *cp == REALM_SEP) || + *cp == COMPONENT_SEP || + *cp == '\0' || *cp == '\\' || *cp == '\t' || + *cp == '\n' || *cp == '\b') + size++; + } + return size; } static int -copy_component_quoting(char *dest, const krb5_data *src) +copy_component_quoting(char *dest, const krb5_data *src, int flags) { int j; const char *cp = src->data; char *q = dest; int length = src->length; + if (flags & KRB5_PRINCIPAL_UNPARSE_DISPLAY) { + memcpy(dest, src->data, src->length); + return src->length; + } + for (j=0; j < length; j++,cp++) { + int no_realm = (flags & KRB5_PRINCIPAL_UNPARSE_NO_REALM) && + !(flags & KRB5_PRINCIPAL_UNPARSE_SHORT); + switch (*cp) { - case COMPONENT_SEP: case REALM_SEP: + if (no_realm) { + *q++ = *cp; + break; + } + case COMPONENT_SEP: case '\\': *q++ = '\\'; *q++ = *cp; @@ -101,6 +120,13 @@ copy_component_quoting(char *dest, const krb5_data *src) *q++ = '\\'; *q++ = 'b'; break; +#if 0 + /* Heimdal escapes spaces in principal names upon unparsing */ + case ' ': + *q++ = '\\'; + *q++ = ' '; + break; +#endif case '\0': *q++ = '\\'; *q++ = '0'; @@ -112,27 +138,47 @@ copy_component_quoting(char *dest, const krb5_data *src) return q - dest; } -krb5_error_code KRB5_CALLCONV -krb5_unparse_name_ext(krb5_context context, krb5_const_principal principal, - char **name, unsigned int *size) +static krb5_error_code +k5_unparse_name(krb5_context context, krb5_const_principal principal, + int flags, char **name, unsigned int *size) { char *cp, *q; int i; int length; krb5_int32 nelem; unsigned int totalsize = 0; + char *default_realm = NULL; + krb5_error_code ret = 0; if (!principal || !name) return KRB5_PARSE_MALFORMED; - totalsize += component_length_quoted(krb5_princ_realm(context, - principal)); - totalsize++; /* This is for the separator */ + if (flags & KRB5_PRINCIPAL_UNPARSE_SHORT) { + /* omit realm if local realm */ + krb5_principal_data p; + + ret = krb5_get_default_realm(context, &default_realm); + if (ret != 0) + goto cleanup; + + krb5_princ_realm(context, &p)->length = strlen(default_realm); + krb5_princ_realm(context, &p)->data = default_realm; + + if (krb5_realm_compare(context, &p, principal)) + flags |= KRB5_PRINCIPAL_UNPARSE_NO_REALM; + } + + if ((flags & KRB5_PRINCIPAL_UNPARSE_NO_REALM) == 0) { + totalsize += component_length_quoted(krb5_princ_realm(context, + principal), + flags); + totalsize++; /* This is for the separator */ + } nelem = krb5_princ_size(context, principal); for (i = 0; i < (int) nelem; i++) { cp = krb5_princ_component(context, principal, i)->data; - totalsize += component_length_quoted(krb5_princ_component(context, principal, i)); + totalsize += component_length_quoted(krb5_princ_component(context, principal, i), flags); totalsize++; /* This is for the separator */ } if (nelem == 0) @@ -143,7 +189,7 @@ krb5_unparse_name_ext(krb5_context context, krb5_const_principal principal, * provided, use it, realloc'ing it if necessary. * * We need only n-1 seperators for n components, but we need - * an extra byte for the NULL at the end. + * an extra byte for the NUL at the end. */ if (size) { if (*name && (*size < totalsize)) { @@ -156,8 +202,10 @@ krb5_unparse_name_ext(krb5_context context, krb5_const_principal principal, *name = malloc(totalsize); } - if (!*name) - return ENOMEM; + if (!*name) { + ret = ENOMEM; + goto cleanup; + } q = *name; @@ -167,24 +215,55 @@ krb5_unparse_name_ext(krb5_context context, krb5_const_principal principal, q += copy_component_quoting(q, krb5_princ_component(context, principal, - i)); + i), + flags); *q++ = COMPONENT_SEP; } if (i > 0) q--; /* Back up last component separator */ - *q++ = REALM_SEP; - q += copy_component_quoting(q, krb5_princ_realm(context, principal)); + if ((flags & KRB5_PRINCIPAL_UNPARSE_NO_REALM) == 0) { + *q++ = REALM_SEP; + q += copy_component_quoting(q, krb5_princ_realm(context, principal), flags); + } *q++ = '\0'; - - return 0; + +cleanup: + if (default_realm != NULL) + krb5_free_default_realm(context, default_realm); + + return ret; } krb5_error_code KRB5_CALLCONV krb5_unparse_name(krb5_context context, krb5_const_principal principal, register char **name) { - if (name) /* name == NULL will return error from _ext */ - *name = NULL; - return(krb5_unparse_name_ext(context, principal, name, NULL)); + if (name != NULL) /* name == NULL will return error from _ext */ + *name = NULL; + + return k5_unparse_name(context, principal, 0, name, NULL); +} + +krb5_error_code KRB5_CALLCONV +krb5_unparse_name_ext(krb5_context context, krb5_const_principal principal, + char **name, unsigned int *size) +{ + return k5_unparse_name(context, principal, 0, name, size); +} + +krb5_error_code KRB5_CALLCONV +krb5_unparse_name_flags(krb5_context context, krb5_const_principal principal, + int flags, char **name) +{ + if (name != NULL) + *name = NULL; + return k5_unparse_name(context, principal, flags, name, NULL); +} + +krb5_error_code KRB5_CALLCONV +krb5_unparse_name_flags_ext(krb5_context context, krb5_const_principal principal, + int flags, char **name, unsigned int *size) +{ + return k5_unparse_name(context, principal, flags, name, size); } diff --git a/src/lib/krb5/krb/valid_times.c b/src/lib/krb5/krb/valid_times.c index 9c53d7d919..febbc369ff 100644 --- a/src/lib/krb5/krb/valid_times.c +++ b/src/lib/krb5/krb/valid_times.c @@ -29,8 +29,6 @@ #include "k5-int.h" -#define in_clock_skew(date) (labs((date)-currenttime) < context->clockskew) - /* * This is an internal routine which validates the krb5_timestamps * field in a krb5_ticket. |