diff options
author | Greg Hudson <ghudson@mit.edu> | 2009-09-13 02:52:23 +0000 |
---|---|---|
committer | Greg Hudson <ghudson@mit.edu> | 2009-09-13 02:52:23 +0000 |
commit | 0e39f8a3ad915eeb0131fb4a87b0fef304101cfd (patch) | |
tree | 6c6d7fd4b23f4724156300b5505433b13cfe9fb6 /src | |
parent | f89b62fe9fd7b0cb10d7e2ff542fb18c1b56d35d (diff) | |
download | krb5-0e39f8a3ad915eeb0131fb4a87b0fef304101cfd.tar.gz krb5-0e39f8a3ad915eeb0131fb4a87b0fef304101cfd.tar.xz krb5-0e39f8a3ad915eeb0131fb4a87b0fef304101cfd.zip |
Implement s4u extensions
Merge Luke's users/lhoward/s4u branch to trunk. Implements S4U2Self
and S4U2Proxy extensions.
ticket: 6563
git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@22736 dc483132-0cff-0310-8789-dd5450dbe970
Diffstat (limited to 'src')
71 files changed, 5306 insertions, 576 deletions
diff --git a/src/clients/kvno/kvno.M b/src/clients/kvno/kvno.M index b7e4d46a0..37b0bcbd5 100644 --- a/src/clients/kvno/kvno.M +++ b/src/clients/kvno/kvno.M @@ -51,6 +51,13 @@ suppress printing .B \-h prints a usage statement and exits .TP +.B \-P +specifies that the +.B service1 service2 ... +arguments are to be treated as services for which credentials should +be acquired using constrained delegation. This option is only valid +when used in conjunction with protocol transition. +.TP .B \-S sname specifies that krb5_sname_to_principal() will be used to build principal names. If this flag is specified, the @@ -59,6 +66,13 @@ arguments are interpreted as hostnames (rather than principal names), and .B sname is interpreted as the service name. +.TP +.B \-U for_user +specifies that protocol transition (S4U2Self) is to be used to acquire +a ticket on behalf of +.B for_user. +If constrained delegation is not requested, the service name +must match the credentials cache client principal. .SH ENVIRONMENT .B Kvno uses the following environment variable: diff --git a/src/clients/kvno/kvno.c b/src/clients/kvno/kvno.c index b98b85d30..58702525f 100644 --- a/src/clients/kvno/kvno.c +++ b/src/clients/kvno/kvno.c @@ -39,8 +39,9 @@ static char *prog; static void xusage() { - fprintf(stderr, "usage: %s [-C] [-u] [-c ccache] [-e etype] [-k keytab] [-S sname] service1 service2 ...\n", - prog); + fprintf(stderr, "usage: %s [-C] [-u] [-c ccache] [-e etype]\n", prog); + fprintf(stderr, "\t[-k keytab] [-S sname] [-U for_user [-P]]\n"); + fprintf(stderr, "\tservice1 service2 ...\n"); exit(1); } @@ -48,7 +49,8 @@ int quiet = 0; static void do_v5_kvno (int argc, char *argv[], char *ccachestr, char *etypestr, char *keytab_name, - char *sname, int canon, int unknown); + char *sname, int canon, int unknown, + char *for_user, int proxy); #include <com_err.h> static void extended_com_err_fn (const char *, errcode_t, const char *, @@ -58,8 +60,8 @@ int main(int argc, char *argv[]) { int option; char *etypestr = NULL, *ccachestr = NULL, *keytab_name = NULL; - char *sname = NULL; - int canon = 0, unknown = 0; + char *sname = NULL, *for_user = NULL; + int canon = 0, unknown = 0, proxy = 0; set_com_err_hook (extended_com_err_fn); @@ -67,7 +69,7 @@ int main(int argc, char *argv[]) prog = strrchr(argv[0], '/'); prog = prog ? (prog + 1) : argv[0]; - while ((option = getopt(argc, argv, "uCc:e:hk:qS:")) != -1) { + while ((option = getopt(argc, argv, "uCc:e:hk:qPS:U:")) != -1) { switch (option) { case 'C': canon = 1; @@ -87,6 +89,9 @@ int main(int argc, char *argv[]) case 'q': quiet = 1; break; + case 'P': + proxy = 1; /* S4U2Proxy - constrained delegation */ + break; case 'S': sname = optarg; if (unknown == 1){ @@ -101,21 +106,37 @@ int main(int argc, char *argv[]) xusage(); } break; + case 'U': + for_user = optarg; /* S4U2Self - protocol transition */ + break; default: xusage(); break; } } + if (proxy) { + if (keytab_name == NULL) { + fprintf(stderr, "Option -P (constrained delegation) " + "requires keytab to be specified\n"); + xusage(); + } else if (for_user == NULL) { + fprintf(stderr, "Option -P (constrained delegation) requires " + "option -U (protocol transition)\n"); + xusage(); + } + } + if ((argc - optind) < 1) xusage(); do_v5_kvno(argc - optind, argv + optind, - ccachestr, etypestr, keytab_name, sname, canon, unknown); + ccachestr, etypestr, keytab_name, sname, + canon, unknown, for_user, proxy); return 0; } -#include <krb5.h> +#include <k5-int.h> static krb5_context context; static void extended_com_err_fn (const char *myprog, errcode_t code, const char *fmt, va_list args) @@ -130,17 +151,18 @@ static void extended_com_err_fn (const char *myprog, errcode_t code, static void do_v5_kvno (int count, char *names[], char * ccachestr, char *etypestr, char *keytab_name, - char *sname, int canon, int unknown) + char *sname, int canon, int unknown, char *for_user, + int proxy) { krb5_error_code ret; int i, errors; krb5_enctype etype; krb5_ccache ccache; krb5_principal me; - krb5_creds in_creds, *out_creds; - krb5_ticket *ticket; - char *princ; + krb5_creds in_creds; krb5_keytab keytab = NULL; + krb5_principal for_user_princ = NULL; + krb5_flags options; ret = krb5_init_context(&context); if (ret) { @@ -175,6 +197,16 @@ static void do_v5_kvno (int count, char *names[], } } + if (for_user) { + ret = krb5_parse_name_flags(context, for_user, + KRB5_PRINCIPAL_PARSE_ENTERPRISE, + &for_user_princ); + if (ret) { + com_err(prog, ret, "while parsing principal name %s", for_user); + exit(1); + } + } + ret = krb5_cc_get_principal(context, ccache, &me); if (ret) { com_err(prog, ret, "while getting client principal name"); @@ -183,91 +215,131 @@ static void do_v5_kvno (int count, char *names[], errors = 0; + options = 0; + if (canon) + options |= KRB5_GC_CANONICALIZE; + for (i = 0; i < count; i++) { - memset(&in_creds, 0, sizeof(in_creds)); + krb5_principal server = NULL; + krb5_ticket *ticket = NULL; + krb5_creds *out_creds = NULL; + char *princ = NULL; - in_creds.client = me; + memset(&in_creds, 0, sizeof(in_creds)); if (sname != NULL) { ret = krb5_sname_to_principal(context, names[i], sname, KRB5_NT_SRV_HST, - &in_creds.server); + &server); } else { - ret = krb5_parse_name(context, names[i], &in_creds.server); + ret = krb5_parse_name(context, names[i], &server); } if (ret) { if (!quiet) com_err(prog, ret, "while parsing principal name %s", names[i]); - errors++; - continue; + goto error; } if (unknown == 1) { - krb5_princ_type(context, in_creds.server) = KRB5_NT_UNKNOWN; + krb5_princ_type(context, server) = KRB5_NT_UNKNOWN; } - ret = krb5_unparse_name(context, in_creds.server, &princ); + ret = krb5_unparse_name(context, server, &princ); if (ret) { com_err(prog, ret, "while formatting parsed principal name for '%s'", names[i]); - errors++; - continue; + goto error; } in_creds.keyblock.enctype = etype; - ret = krb5_get_credentials(context, canon ? KRB5_GC_CANONICALIZE : 0, - ccache, &in_creds, &out_creds); + if (for_user) { + if (!proxy && + !krb5_principal_compare(context, me, server)) { + com_err(prog, EINVAL, + "client and server principal names must match"); + goto error; + } - krb5_free_principal(context, in_creds.server); + in_creds.client = for_user_princ; + in_creds.server = me; + + ret = krb5_get_credentials_for_user(context, options, ccache, + &in_creds, NULL, &out_creds); + } else { + in_creds.client = me; + in_creds.server = server; + ret = krb5_get_credentials(context, options, ccache, + &in_creds, &out_creds); + } if (ret) { com_err(prog, ret, "while getting credentials for %s", princ); - - krb5_free_unparsed_name(context, princ); - - errors++; - continue; + goto error; } /* we need a native ticket */ ret = krb5_decode_ticket(&out_creds->ticket, &ticket); if (ret) { com_err(prog, ret, "while decoding ticket for %s", princ); - krb5_free_creds(context, out_creds); - krb5_free_unparsed_name(context, princ); - - errors++; - continue; + goto error; } - + if (keytab) { ret = krb5_server_decrypt_ticket_keytab(context, keytab, ticket); if (ret) { - if (!quiet) - printf("%s: kvno = %d, keytab entry invalid", princ, ticket->enc_part.kvno); + if (!quiet) { + fprintf(stderr, "%s: kvno = %d, keytab entry invalid\n", + princ, ticket->enc_part.kvno); + } com_err(prog, ret, "while decrypting ticket for %s", princ); - krb5_free_ticket(context, ticket); - krb5_free_creds(context, out_creds); - krb5_free_unparsed_name(context, princ); - - errors++; - continue; + goto error; } if (!quiet) - printf("%s: kvno = %d, keytab entry valid\n", princ, ticket->enc_part.kvno); + printf("%s: kvno = %d, keytab entry valid\n", + princ, ticket->enc_part.kvno); + if (proxy) { + krb5_free_creds(context, out_creds); + out_creds = NULL; + + in_creds.client = ticket->enc_part2->client; + in_creds.server = server; + + ret = krb5_get_credentials_for_proxy(context, + KRB5_GC_CANONICALIZE, + ccache, + &in_creds, + ticket, + &out_creds); + if (ret) { + com_err(prog, ret, + "%s: constrained delegation failed", princ); + goto error; + } + } } else { if (!quiet) printf("%s: kvno = %d\n", princ, ticket->enc_part.kvno); } - krb5_free_creds(context, out_creds); - krb5_free_unparsed_name(context, princ); + continue; + +error: + if (server != NULL) + krb5_free_principal(context, server); + if (ticket != NULL) + krb5_free_ticket(context, ticket); + if (out_creds != NULL) + krb5_free_creds(context, out_creds); + if (princ != NULL) + krb5_free_unparsed_name(context, princ); + errors++; } if (keytab) krb5_kt_close(context, keytab); krb5_free_principal(context, me); + krb5_free_principal(context, for_user_princ); krb5_cc_close(context, ccache); krb5_free_context(context); diff --git a/src/include/k5-int.h b/src/include/k5-int.h index 90b6d9cf7..dbe522356 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -966,6 +966,21 @@ typedef struct _krb5_pa_for_user { krb5_data auth_package; } krb5_pa_for_user; +typedef struct _krb5_s4u_userid { + krb5_int32 nonce; + krb5_principal user; + krb5_data subject_cert; + krb5_flags options; +} krb5_s4u_userid; + +#define KRB5_S4U_OPTS_CHECK_LOGON_HOURS 0x40000000 /* check logon hour restrictions */ +#define KRB5_S4U_OPTS_USE_REPLY_KEY_USAGE 0x20000000 /* sign with usage 27 instead of 26 */ + +typedef struct _krb5_pa_s4u_x509_user { + krb5_s4u_userid user_id; + krb5_checksum cksum; +} krb5_pa_s4u_x509_user; + enum { KRB5_FAST_ARMOR_AP_REQUEST = 0x1 }; @@ -1295,6 +1310,10 @@ void KRB5_CALLCONV krb5_free_pa_enc_ts (krb5_context, krb5_pa_enc_ts *); void KRB5_CALLCONV krb5_free_pa_for_user (krb5_context, krb5_pa_for_user * ); +void KRB5_CALLCONV krb5_free_s4u_userid_contents + (krb5_context, krb5_s4u_userid * ); +void KRB5_CALLCONV krb5_free_pa_s4u_x509_user + (krb5_context, krb5_pa_s4u_x509_user * ); void KRB5_CALLCONV krb5_free_pa_svr_referral_data (krb5_context, krb5_pa_svr_referral_data * ); void KRB5_CALLCONV krb5_free_pa_server_referral_data @@ -1609,6 +1628,12 @@ krb5_error_code encode_krb5_setpw_req krb5_error_code encode_krb5_pa_for_user (const krb5_pa_for_user * , krb5_data **); +krb5_error_code encode_krb5_s4u_userid + (const krb5_s4u_userid * , krb5_data **); + +krb5_error_code encode_krb5_pa_s4u_x509_user + (const krb5_pa_s4u_x509_user * , krb5_data **); + krb5_error_code encode_krb5_pa_svr_referral_data (const krb5_pa_svr_referral_data * , krb5_data **); @@ -1778,6 +1803,9 @@ krb5_error_code decode_krb5_setpw_req krb5_error_code decode_krb5_pa_for_user (const krb5_data *, krb5_pa_for_user **); +krb5_error_code decode_krb5_pa_s4u_x509_user + (const krb5_data *, krb5_pa_s4u_x509_user **); + krb5_error_code decode_krb5_pa_svr_referral_data (const krb5_data *, krb5_pa_svr_referral_data **); @@ -2606,6 +2634,11 @@ krb5_error_code krb5int_send_tgs krb5_pa_data * const *, const krb5_data *, krb5_creds *, + krb5_error_code (*gcvt_fct)(krb5_context, + krb5_keyblock *, + krb5_kdc_req *, + void *), + void *gcvt_data, krb5_response * , krb5_keyblock **subkey); /* The subkey field is an output parameter; if a * tgs-rep is received then the subkey will be filled @@ -2796,6 +2829,21 @@ krb5int_pac_sign(krb5_context context, const krb5_keyblock *privsvr_key, krb5_data *data); +krb5_error_code KRB5_CALLCONV +krb5_get_credentials_for_user(krb5_context context, krb5_flags options, + krb5_ccache ccache, + krb5_creds *in_creds, + krb5_data *cert, + krb5_creds **out_creds); + +krb5_error_code KRB5_CALLCONV +krb5_get_credentials_for_proxy(krb5_context context, + krb5_flags options, + krb5_ccache ccache, + krb5_creds *in_creds, + krb5_ticket *evidence_tkt, + krb5_creds **out_creds); + krb5_error_code krb5int_parse_enctype_list(krb5_context context, char *profstr, krb5_enctype *default_list, krb5_enctype **result); diff --git a/src/include/kdb.h b/src/include/kdb.h index ea81cfeef..8c0cd247a 100644 --- a/src/include/kdb.h +++ b/src/include/kdb.h @@ -96,6 +96,8 @@ #define KRB5_KDB_SUPPORT_DESMD5 0x00004000 #define KRB5_KDB_NEW_PRINC 0x00008000 #define KRB5_KDB_OK_AS_DELEGATE 0x00100000 +#define KRB5_KDB_OK_TO_AUTH_AS_DELEGATE 0x00200000 /* S4U2Self OK */ +#define KRB5_KDB_NO_AUTH_DATA_REQUIRED 0x00400000 /* Creation flags */ #define KRB5_KDB_CREATE_BTREE 0x00000001 diff --git a/src/include/kdb_ext.h b/src/include/kdb_ext.h index 59323e232..348be5127 100644 --- a/src/include/kdb_ext.h +++ b/src/include/kdb_ext.h @@ -31,10 +31,6 @@ #ifndef KRB5_KDB5_EXT__ #define KRB5_KDB5_EXT__ -/* Allowed to use protocol transition */ -#define KRB5_KDB_OK_TO_AUTH_AS_DELEGATE 0x00200000 -/* Service does not require authorization data */ -#define KRB5_KDB_NO_AUTH_DATA_REQUIRED 0x00400000 /* Private flag used to indicate principal is local TGS */ #define KRB5_KDB_TICKET_GRANTING_SERVICE 0x01000000 /* Private flag used to indicate xrealm relationship is non-transitive */ diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin index bf8c29c59..8111c5bb6 100644 --- a/src/include/krb5/krb5.hin +++ b/src/include/krb5/krb5.hin @@ -631,6 +631,11 @@ krb5_error_code KRB5_CALLCONV /* Defined in KDC referrals draft */ #define KRB5_KEYUSAGE_PA_REFERRAL 26 /* XXX note conflict with above */ + +/* Defined in [MS-SFU] */ +#define KRB5_KEYUSAGE_PA_S4U_X509_USER_REQUEST 26 /* XXX note conflict with above */ +#define KRB5_KEYUSAGE_PA_S4U_X509_USER_REPLY 27 /* XXX note conflict with above */ + /* define in draft-ietf-krb-wg-preauth-framework*/ #define KRB5_KEYUSAGE_FAST_REQ_CHKSUM 50 #define KRB5_KEYUSAGE_FAST_ENC 51 @@ -1566,6 +1571,10 @@ void KRB5_CALLCONV krb5_free_tgt_creds #define KRB5_GC_USER_USER 1 /* want user-user ticket */ #define KRB5_GC_CACHED 2 /* want cached ticket only */ #define KRB5_GC_CANONICALIZE 4 /* set canonicalize KDC option */ +#define KRB5_GC_NO_STORE 8 /* do not store in credentials cache */ +#define KRB5_GC_FORWARDABLE 16 /* acquire forwardable tickets */ +#define KRB5_GC_NO_TRANSIT_CHECK 32 /* disable transited check */ +#define KRB5_GC_CONSTRAINED_DELEGATION 64 /* constrained delegation */ krb5_error_code KRB5_CALLCONV krb5_get_credentials (krb5_context, diff --git a/src/kadmin/cli/kadmin.c b/src/kadmin/cli/kadmin.c index 814ace35c..513e716bb 100644 --- a/src/kadmin/cli/kadmin.c +++ b/src/kadmin/cli/kadmin.c @@ -72,7 +72,9 @@ static struct pflag flags[] = { {"allow_svr", 9, KRB5_KDB_DISALLOW_SVR, 1}, {"password_changing_service", 25, KRB5_KDB_PWCHANGE_SERVICE, 0 }, {"support_desmd5", 14, KRB5_KDB_SUPPORT_DESMD5, 0 }, -{"ok_as_delegate", 14, KRB5_KDB_OK_AS_DELEGATE, 0 } +{"ok_as_delegate", 14, KRB5_KDB_OK_AS_DELEGATE, 0 }, +{"ok_to_auth_as_delegate", 22, KRB5_KDB_OK_TO_AUTH_AS_DELEGATE, 0 }, +{"no_auth_data_required", 21, KRB5_KDB_NO_AUTH_DATA_REQUIRED, 0}, }; static char *prflags[] = { @@ -97,6 +99,8 @@ static char *prflags[] = { "UNKNOWN_0x00040000", /* 0x00040000 */ "UNKNOWN_0x00080000", /* 0x00080000 */ "OK_AS_DELEGATE", /* 0x00100000 */ + "OK_TO_AUTH_AS_DELEGATE", /* 0x00200000 */ + "NO_AUTH_DATA_REQUIRED", /* 0x00400000 */ }; char *getenv(); @@ -1123,7 +1127,7 @@ kadmin_addprinc_usage(func) "\t\tallow_postdated allow_forwardable allow_tgs_req allow_renewable\n", "\t\tallow_proxiable allow_dup_skey allow_tix requires_preauth\n", "\t\trequires_hwauth needchange allow_svr password_changing_service\n" - "\t\tok_as_delegate\n" + "\t\tok_as_delegate ok_to_auth_as_delegate no_auth_data_required\n" "\nwhere,\n\t[-x db_princ_args]* - any number of database specific arguments.\n" "\t\t\tLook at each database documentation for supported arguments\n"); } @@ -1140,7 +1144,7 @@ kadmin_modprinc_usage(func) "\t\tallow_postdated allow_forwardable allow_tgs_req allow_renewable\n", "\t\tallow_proxiable allow_dup_skey allow_tix requires_preauth\n", "\t\trequires_hwauth needchange allow_svr password_changing_service\n" - "\t\tok_as_delegate\n" + "\t\tok_as_delegate ok_to_auth_as_delegate no_auth_data_required\n" "\nwhere,\n\t[-x db_princ_args]* - any number of database specific arguments.\n" "\t\t\tLook at each database documentation for supported arguments\n" ); diff --git a/src/kdc/do_tgs_req.c b/src/kdc/do_tgs_req.c index a99dc35ba..37a69e10d 100644 --- a/src/kdc/do_tgs_req.c +++ b/src/kdc/do_tgs_req.c @@ -117,7 +117,7 @@ process_tgs_req(krb5_data *pkt, const krb5_fulladdr *from, krb5_enc_tkt_part *header_enc_tkt = NULL; /* ticket granting or evidence ticket */ krb5_db_entry client, krbtgt; int c_nprincs = 0, k_nprincs = 0; - krb5_pa_for_user *for_user = NULL; /* protocol transition request */ + krb5_pa_s4u_x509_user *s4u_x509_user = NULL; /* protocol transition request */ krb5_authdata **kdc_issued_auth_data = NULL; /* auth data issued by KDC */ unsigned int c_flags = 0, s_flags = 0; /* client/server KDB flags */ char *s4u_name = NULL; @@ -131,7 +131,7 @@ process_tgs_req(krb5_data *pkt, const krb5_fulladdr *from, krb5_data scratch; session_key.contents = NULL; - + retval = decode_krb5_tgs_req(pkt, &request); if (retval) return retval; @@ -292,12 +292,20 @@ tgt_again: !krb5_principal_compare(kdc_context, tgs_server, server.princ); /* Check for protocol transition */ - errcode = kdc_process_s4u2self_req(kdc_context, request, header_enc_tkt->client, - &server, header_enc_tkt->session, kdc_time, - &for_user, &client, &c_nprincs, &status); + errcode = kdc_process_s4u2self_req(kdc_context, + request, + header_enc_tkt->client, + &server, + subkey, + header_enc_tkt->session, + kdc_time, + &s4u_x509_user, + &client, + &c_nprincs, + &status); if (errcode) goto cleanup; - if (for_user != NULL) + if (s4u_x509_user != NULL) setflag(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION); /* @@ -438,19 +446,32 @@ tgt_again: /* processing of any of these flags. For example, some */ /* realms may refuse to issue renewable tickets */ - if (isflagset(request->kdc_options, KDC_OPT_FORWARDABLE)) + if (isflagset(request->kdc_options, KDC_OPT_FORWARDABLE)) { setflag(enc_tkt_reply.flags, TKT_FLG_FORWARDABLE); - if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION)) { - if (!krb5_is_tgs_principal(server.princ) && - is_local_principal(server.princ)) { - if (isflagset(server.attributes, KRB5_KDB_OK_TO_AUTH_AS_DELEGATE)) - setflag(enc_tkt_reply.flags, TKT_FLG_FORWARDABLE); - else + + if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION)) { + /* + * If S4U2Self principal is not forwardable, then mark ticket as + * unforwardable. This behaviour matches Windows, but it is + * different to the MIT AS-REQ path, which returns an error + * (KDC_ERR_POLICY) if forwardable tickets cannot be issued. + * + * Consider this block the S4U2Self equivalent to + * validate_forwardable(). + */ + if (c_nprincs && + isflagset(client.attributes, KRB5_KDB_DISALLOW_FORWARDABLE)) + clear(enc_tkt_reply.flags, TKT_FLG_FORWARDABLE); + /* + * OK_TO_AUTH_AS_DELEGATE must be set on the service requesting + * S4U2Self in order for forwardable tickets to be returned. + */ + else if (!is_referral && + !isflagset(server.attributes, KRB5_KDB_OK_TO_AUTH_AS_DELEGATE)) clear(enc_tkt_reply.flags, TKT_FLG_FORWARDABLE); } - if (isflagset(client.attributes, KRB5_KDB_DISALLOW_FORWARDABLE)) - clear(enc_tkt_reply.flags, TKT_FLG_FORWARDABLE); } + if (isflagset(request->kdc_options, KDC_OPT_FORWARDED)) { setflag(enc_tkt_reply.flags, TKT_FLG_FORWARDED); @@ -560,7 +581,7 @@ tgt_again: enc_tkt_reply.times.starttime = 0; if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION)) { - errcode = krb5_unparse_name(kdc_context, for_user->user, &s4u_name); + errcode = krb5_unparse_name(kdc_context, s4u_x509_user->user_id.user, &s4u_name); } else if (isflagset(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION)) { errcode = krb5_unparse_name(kdc_context, header_enc_tkt->client, &s4u_name); } else { @@ -670,8 +691,8 @@ tgt_again: enc_tkt_reply.authorization_data = NULL; if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION) && - is_local_principal(header_enc_tkt->client)) - enc_tkt_reply.client = for_user->user; + !isflagset(c_flags, KRB5_KDB_FLAG_CROSS_REALM)) + enc_tkt_reply.client = s4u_x509_user->user_id.user; else enc_tkt_reply.client = header_enc_tkt->client; @@ -685,7 +706,8 @@ tgt_again: &encrypting_key, /* U2U or server key */ pkt, request, - for_user ? for_user->user : NULL, + s4u_x509_user ? + s4u_x509_user->user_id.user : NULL, header_enc_tkt, &enc_tkt_reply); if (errcode) { @@ -845,6 +867,20 @@ tgt_again: /* Start assembling the response */ reply.msg_type = KRB5_TGS_REP; reply.padata = 0;/* always */ + if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION) && + find_pa_data(request->padata, KRB5_PADATA_S4U_X509_USER) != NULL) { + errcode = kdc_make_s4u2self_rep(kdc_context, + subkey, + header_ticket->enc_part2->session, + s4u_x509_user, + &reply, + &reply_encpart); + if (errcode) { + status = "KDC_RETURN_S4U2SELF_PADATA"; + goto cleanup; + } + } + reply.client = enc_tkt_reply.client; reply.enc_part.kvno = 0;/* We are using the session key */ reply.ticket = &ticket_reply; @@ -958,14 +994,18 @@ cleanup: krb5_db_free_principal(kdc_context, &krbtgt, k_nprincs); if (c_nprincs) krb5_db_free_principal(kdc_context, &client, c_nprincs); - if (for_user != NULL) - krb5_free_pa_for_user(kdc_context, for_user); + if (s4u_x509_user != NULL) + krb5_free_pa_s4u_x509_user(kdc_context, s4u_x509_user); if (kdc_issued_auth_data != NULL) krb5_free_authdata(kdc_context, kdc_issued_auth_data); if (s4u_name != NULL) free(s4u_name); if (subkey != NULL) krb5_free_keyblock(kdc_context, subkey); + if (reply.padata) + krb5_free_pa_data(kdc_context, reply.padata); + if (reply_encpart.enc_padata) + krb5_free_pa_data(kdc_context, reply_encpart.enc_padata); return retval; } diff --git a/src/kdc/kdc_authdata.c b/src/kdc/kdc_authdata.c index 43ea0869a..504d3fbdd 100644 --- a/src/kdc/kdc_authdata.c +++ b/src/kdc/kdc_authdata.c @@ -544,9 +544,18 @@ handle_tgt_authdata (krb5_context context, } if (ad_nprincs != 0) { + /* + * This code was submitted by Novell; however there is no + * mention in [MS-SFU] of needing to examine the authorization + * data to clear the forwardable flag. My understanding is that + * the state of the forwardable flag is propagated through the + * cross-realm TGTs. + */ +#if 0 if (isflagset(flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION) && isflagset(ad_entry.attributes, KRB5_KDB_DISALLOW_FORWARDABLE)) clear(enc_tkt_reply->flags, TKT_FLG_FORWARDABLE); +#endif krb5_db_free_principal(context, &ad_entry, ad_nprincs); diff --git a/src/kdc/kdc_preauth.c b/src/kdc/kdc_preauth.c index cc7ae34ed..2149fd1ac 100644 --- a/src/kdc/kdc_preauth.c +++ b/src/kdc/kdc_preauth.c @@ -1349,25 +1349,6 @@ cleanup: } static krb5_boolean -enctype_requires_etype_info_2(krb5_enctype enctype) -{ - switch(enctype) { - case ENCTYPE_DES_CBC_CRC: - case ENCTYPE_DES_CBC_MD4: - case ENCTYPE_DES_CBC_MD5: - case ENCTYPE_DES3_CBC_SHA1: - case ENCTYPE_DES3_CBC_RAW: - case ENCTYPE_ARCFOUR_HMAC: - case ENCTYPE_ARCFOUR_HMAC_EXP : - return 0; - default: - if (krb5_c_valid_enctype(enctype)) - return 1; - else return 0; - } -} - -static krb5_boolean request_contains_enctype (krb5_context context, const krb5_kdc_req *request, krb5_enctype enctype) { diff --git a/src/kdc/kdc_util.c b/src/kdc/kdc_util.c index 88ef11062..6ac528953 100644 --- a/src/kdc/kdc_util.c +++ b/src/kdc/kdc_util.c @@ -223,7 +223,7 @@ comp_cksum(krb5_context kcontext, krb5_data *source, krb5_ticket *ticket, krb5_pa_data * find_pa_data(krb5_pa_data **padata, krb5_preauthtype pa_type) { -return krb5int_find_pa_data(kdc_context, padata, pa_type); + return krb5int_find_pa_data(kdc_context, padata, pa_type); } krb5_error_code @@ -371,7 +371,8 @@ kdc_process_tgs_req(krb5_kdc_req *request, const krb5_fulladdr *from, } /* make sure the client is of proper lineage (see above) */ - if (foreign_server && !find_pa_data(request->padata, KRB5_PADATA_FOR_USER)) { + if (foreign_server && + !find_pa_data(request->padata, KRB5_PADATA_FOR_USER)) { if (is_local_principal((*ticket)->enc_part2->client)) { /* someone in a foreign realm claiming to be local */ krb5_klog_syslog(LOG_INFO, "PROCESS_TGS: failed lineage check"); @@ -926,7 +927,8 @@ fail: * as a com_err error number! */ #define AS_INVALID_OPTIONS (KDC_OPT_FORWARDED | KDC_OPT_PROXY |\ -KDC_OPT_VALIDATE | KDC_OPT_RENEW | KDC_OPT_ENC_TKT_IN_SKEY) + KDC_OPT_VALIDATE | KDC_OPT_RENEW | \ + KDC_OPT_ENC_TKT_IN_SKEY | KDC_OPT_CNAME_IN_ADDL_TKT) int validate_as_request(register krb5_kdc_req *request, krb5_db_entry client, krb5_db_entry server, krb5_timestamp kdc_time, @@ -998,17 +1000,9 @@ validate_as_request(register krb5_kdc_req *request, krb5_db_entry client, * preauthentication data is absent in the request. * * Hence, this check most be done after the check for preauth - * data, and is now performed by validate_forwardable(). + * data, and is now performed by validate_forwardable() (the + * contents of which were previously below). */ -#if 0 - /* Client and server must allow forwardable tickets */ - if (isflagset(request->kdc_options, KDC_OPT_FORWARDABLE) && - (isflagset(client.attributes, KRB5_KDB_DISALLOW_FORWARDABLE) || - isflagset(server.attributes, KRB5_KDB_DISALLOW_FORWARDABLE))) { - *status = "FORWARDABLE NOT ALLOWED"; - return(KDC_ERR_POLICY); - } -#endif /* Client and server must allow renewable tickets */ if (isflagset(request->kdc_options, KDC_OPT_RENEWABLE) && @@ -1793,7 +1787,7 @@ sign_db_authdata (krb5_context context, } static krb5_error_code -verify_s4u2self_checksum(krb5_context context, +verify_for_user_checksum(krb5_context context, krb5_keyblock *key, krb5_pa_for_user *req) { @@ -1852,7 +1846,7 @@ verify_s4u2self_checksum(krb5_context context, &valid); if (code == 0 && valid == FALSE) - code = KRB5KRB_AP_ERR_BAD_INTEGRITY; + code = KRB5KRB_AP_ERR_MODIFIED; free(data.data); @@ -1860,55 +1854,246 @@ verify_s4u2self_checksum(krb5_context context, } /* - * Protocol transition validation code based on AS-REQ - * validation code + * Legacy protocol transition (Windows 2003 and above) */ -static int -validate_s4u2self_request(krb5_kdc_req *request, - const krb5_db_entry *client, - krb5_timestamp kdc_time, - const char **status) +static krb5_error_code +kdc_process_for_user(krb5_context context, + krb5_pa_data *pa_data, + krb5_keyblock *tgs_session, + krb5_pa_s4u_x509_user **s4u_x509_user, + const char **status) { - int errcode; - krb5_db_entry server = { 0 }; - - /* The client must not be expired */ - if (client->expiration && client->expiration < kdc_time) { - *status = "CLIENT EXPIRED"; - return KDC_ERR_NAME_EXP; + krb5_error_code code; + krb5_pa_for_user *for_user; + krb5_data req_data; + + req_data.length = pa_data->length; + req_data.data = (char *)pa_data->contents; + + code = decode_krb5_pa_for_user(&req_data, &for_user); + if (code) + return code; + + code = verify_for_user_checksum(context, tgs_session, for_user); + if (code) { + *status = "INVALID_S4U2SELF_CHECKSUM"; + krb5_free_pa_for_user(kdc_context, for_user); + return code; } - /* The client's password must not be expired, unless the server is - a KRB5_KDC_PWCHANGE_SERVICE. */ - if (client->pw_expiration && client->pw_expiration < kdc_time) { - *status = "CLIENT KEY EXPIRED"; - return KDC_ERR_KEY_EXP; + *s4u_x509_user = calloc(1, sizeof(krb5_pa_s4u_x509_user)); + if (*s4u_x509_user == NULL) { + krb5_free_pa_for_user(kdc_context, for_user); + return ENOMEM; } + (*s4u_x509_user)->user_id.user = for_user->user; + for_user->user = NULL; + krb5_free_pa_for_user(context, for_user); + + return 0; +} + +static krb5_error_code +verify_s4u_x509_user_checksum(krb5_context context, + krb5_keyblock *key, + krb5_data *req_data, + krb5_int32 kdc_req_nonce, + krb5_pa_s4u_x509_user *req) +{ + krb5_error_code code; + krb5_data scratch; + krb5_boolean valid = FALSE; + + if (enctype_requires_etype_info_2(key->enctype) && + !krb5_c_is_keyed_cksum(req->cksum.checksum_type)) + return KRB5KRB_AP_ERR_INAPP_CKSUM; + + if (req->user_id.nonce != kdc_req_nonce) + return KRB5KRB_AP_ERR_MODIFIED; + /* - * If the client requires password changing, then return an - * error; S4U2Self cannot be used to change a password. + * Verify checksum over the encoded userid. If that fails, + * re-encode, and verify that. This is similar to the + * behaviour in kdc_process_tgs_req(). */ - if (isflagset(client->attributes, KRB5_KDB_REQUIRES_PWCHANGE)) { - *status = "REQUIRED PWCHANGE"; - return KDC_ERR_KEY_EXP; + if (fetch_asn1_field((unsigned char *)req_data->data, 1, 0, &scratch) < 0) + return ASN1_PARSE_ERROR; + + code = krb5_c_verify_checksum(context, + key, + KRB5_KEYUSAGE_PA_S4U_X509_USER_REQUEST, + &scratch, + &req->cksum, + &valid); + if (code != 0) + return code; + + if (valid == FALSE) { + krb5_data *data; + + code = encode_krb5_s4u_userid(&req->user_id, &data); + if (code != 0) + return code; + + code = krb5_c_verify_checksum(context, + key, + KRB5_KEYUSAGE_PA_S4U_X509_USER_REQUEST, + data, + &req->cksum, + &valid); + + krb5_free_data(context, data); + + if (code != 0) + return code; } - /* Check to see if client is locked out */ - if (isflagset(client->attributes, KRB5_KDB_DISALLOW_ALL_TIX)) { - *status = "CLIENT LOCKED OUT"; - return KDC_ERR_C_PRINCIPAL_UNKNOWN; + return valid ? 0 : KRB5KRB_AP_ERR_MODIFIED; +} + +/* + * New protocol transition request (Windows 2008 and above) + */ +static krb5_error_code +kdc_process_s4u_x509_user(krb5_context context, + krb5_kdc_req *request, + krb5_pa_data *pa_data, + krb5_keyblock *tgs_subkey, + krb5_keyblock *tgs_session, + krb5_pa_s4u_x509_user **s4u_x509_user, + const char **status) +{ + krb5_error_code code; + krb5_data req_data; + + req_data.length = pa_data->length; + req_data.data = (char *)pa_data->contents; + + code = decode_krb5_pa_s4u_x509_user(&req_data, s4u_x509_user); + if (code) + return code; + + code = verify_s4u_x509_user_checksum(context, + tgs_subkey ? tgs_subkey : + tgs_session, + &req_data, + request->nonce, *s4u_x509_user); + + if (code) { + *status = "INVALID_S4U2SELF_CHECKSUM"; + krb5_free_pa_s4u_x509_user(context, *s4u_x509_user); + *s4u_x509_user = NULL; + return code; } + if (krb5_princ_size(context, (*s4u_x509_user)->user_id.user) == 0 || + (*s4u_x509_user)->user_id.subject_cert.length != 0) { + *status = "INVALID_S4U2SELF_REQUEST"; + krb5_free_pa_s4u_x509_user(context, *s4u_x509_user); + *s4u_x509_user = NULL; + return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; + } + + return 0; +} + +krb5_error_code +kdc_make_s4u2self_rep(krb5_context context, + krb5_keyblock *tgs_subkey, + krb5_keyblock *tgs_session, + krb5_pa_s4u_x509_user *req_s4u_user, + krb5_kdc_rep *reply, + krb5_enc_kdc_rep_part *reply_encpart) +{ + krb5_error_code code; + krb5_data *data = NULL; + krb5_pa_s4u_x509_user rep_s4u_user; + krb5_pa_data padata; + krb5_enctype enctype; + krb5_keyusage usage; + + memset(&rep_s4u_user, 0, sizeof(rep_s4u_user)); + + rep_s4u_user.user_id.nonce = req_s4u_user->user_id.nonce; + rep_s4u_user.user_id.user = req_s4u_user->user_id.user; + rep_s4u_user.user_id.options = + req_s4u_user->user_id.options & KRB5_S4U_OPTS_USE_REPLY_KEY_USAGE; + + code = encode_krb5_s4u_userid(&rep_s4u_user.user_id, &data); + if (code != 0) + goto cleanup; + + if (req_s4u_user->user_id.options & KRB5_S4U_OPTS_USE_REPLY_KEY_USAGE) + usage = KRB5_KEYUSAGE_PA_S4U_X509_USER_REPLY; + else + usage = KRB5_KEYUSAGE_PA_S4U_X509_USER_REQUEST; + + code = krb5_c_make_checksum(context, req_s4u_user->cksum.checksum_type, + tgs_subkey != NULL ? tgs_subkey : tgs_session, + usage, data, + &rep_s4u_user.cksum); + if (code != 0) + goto cleanup; + + krb5_free_data(context, data); + data = NULL; + + code = encode_krb5_pa_s4u_x509_user(&rep_s4u_user, &data); + if (code != 0) + goto cleanup; + + padata.magic = KV5M_PA_DATA; + padata.pa_type = KRB5_PADATA_S4U_X509_USER; + padata.length = data->length; + padata.contents = (krb5_octet *)data->data; + + code = add_pa_data_element(context, &padata, &reply->padata, FALSE); + if (code != 0) + goto cleanup; + + free(data); + data = NULL; + + if (tgs_subkey != NULL) + enctype = tgs_subkey->enctype; + else + enctype = tgs_session->enctype; + /* - * Check against local policy + * Owing to a bug in Windows, unkeyed checksums were used for older + * enctypes, including rc4-hmac. A forthcoming workaround for this + * includes the checksum bytes in the encrypted padata. */ - errcode = against_local_policy_as(request, *client, server, - kdc_time, status); - if (errcode) - return errcode; + if ((req_s4u_user->user_id.options & KRB5_S4U_OPTS_USE_REPLY_KEY_USAGE) && + enctype_requires_etype_info_2(enctype) == FALSE) { + padata.length = req_s4u_user->cksum.length + + rep_s4u_user.cksum.length; + padata.contents = malloc(padata.length); + if (padata.contents == NULL) { + code = ENOMEM; + goto cleanup; + } - return 0; + memcpy(padata.contents, + req_s4u_user->cksum.contents, + req_s4u_user->cksum.length); + memcpy(&padata.contents[req_s4u_user->cksum.length], + rep_s4u_user.cksum.contents, + rep_s4u_user.cksum.length); + + code = add_pa_data_element(context,&padata, + &reply_encpart->enc_padata, FALSE); + if (code != 0) + goto cleanup; + } + +cleanup: + if (rep_s4u_user.cksum.contents != NULL) + krb5_free_checksum_contents(context, &rep_s4u_user.cksum); + krb5_free_data(context, data); + + return code; } /* @@ -1919,92 +2104,116 @@ kdc_process_s4u2self_req(krb5_context context, krb5_kdc_req *request, krb5_const_principal client_princ, const krb5_db_entry *server, - krb5_keyblock *subkey, + krb5_keyblock *tgs_subkey, + krb5_keyblock *tgs_session, krb5_timestamp kdc_time, - krb5_pa_for_user **for_user, + krb5_pa_s4u_x509_user **s4u_x509_user, krb5_db_entry *princ, int *nprincs, const char **status) { krb5_error_code code; - krb5_pa_data **pa_data; - krb5_data req_data; + krb5_pa_data *pa_data; krb5_boolean more; + int flags; *nprincs = 0; memset(princ, 0, sizeof(*princ)); - if (request->padata == NULL) { - return 0; - } - - for (pa_data = request->padata; *pa_data != NULL; pa_data++) { - if ((*pa_data)->pa_type == KRB5_PADATA_FOR_USER) - break; - } - if (*pa_data == NULL) { - return 0; + pa_data = find_pa_data(request->padata, KRB5_PADATA_S4U_X509_USER); + if (pa_data != NULL) { + code = kdc_process_s4u_x509_user(context, + request, + pa_data, + tgs_subkey, + tgs_session, + s4u_x509_user, + status); + if (code != 0) + return code; + } else { + pa_data = find_pa_data(request->padata, KRB5_PADATA_FOR_USER); + if (pa_data != NULL) { + code = kdc_process_for_user(context, + pa_data, + tgs_session, + s4u_x509_user, + status); + if (code != 0) + return code; + } else + return 0; } -#if 0 /* - * Ignore request if the server principal is a TGS, not so much - * to avoid unconstrained tickets being issued (as that would - * require knowing the TGS key anyway) but so that we do not - * block the server referral path. + * We need to compare the client name in the TGT with the requested + * server name. Supporting server name aliases without assuming a + * global name service makes this difficult to do. + * + * The comparison below handles the following cases (note that the + * term "principal name" below excludes the realm). + * + * (1) The requested service is a host-based service with two name + * components, in which case we assume the principal name to + * contain sufficient qualifying information. The realm is + * ignored for the purpose of comparison. + * + * (2) The requested service name is an enterprise principal name: + * the service principal name is compared with the unparsed + * form of the client name (including its realm). + * + * (3) The requested service is some other name type: an exact + * match is required. + * + * An alternative would be to look up the server once again with + * FLAG_CANONICALIZE | FLAG_CLIENT_REFERRALS_ONLY set, do an exact + * match between the returned name and client_princ. However, this + * assumes that the client set FLAG_CANONICALIZE when requesting + * the TGT and that we have a global name service. */ - if (krb5_is_tgs_principal(server->princ)) { - return 0; - } -#endif - - *status = "PROCESS_S4U2SELF_REQUEST"; - - req_data.length = (*pa_data)->length; - req_data.data = (char *)(*pa_data)->contents; - - code = decode_krb5_pa_for_user(&req_data, for_user); - if (code) { - return code; - } - - if (krb5_princ_type(context, (*for_user)->user) != - KRB5_NT_ENTERPRISE_PRINCIPAL) { - *status = "INVALID_S4U2SELF_REQUEST"; - return KRB5KDC_ERR_POLICY; + flags = 0; + switch (krb5_princ_type(kdc_context, request->server)) { + case KRB5_NT_SRV_HST: /* (1) */ + if (krb5_princ_size(kdc_context, request->server) == 2) + flags |= KRB5_PRINCIPAL_COMPARE_IGNORE_REALM; + break; + case KRB5_NT_ENTERPRISE_PRINCIPAL: /* (2) */ + flags |= KRB5_PRINCIPAL_COMPARE_ENTERPRISE; + break; + default: /* (3) */ + break; } - code = verify_s4u2self_checksum(context, subkey, *for_user); - if (code) { - *status = "INVALID_S4U2SELF_CHECKSUM"; - krb5_free_pa_for_user(kdc_context, *for_user); - *for_user = NULL; - return code; - } - if (!krb5_principal_compare_flags(context, request->server, client_princ, - KRB5_PRINCIPAL_COMPARE_ENTERPRISE)) { + if (!krb5_principal_compare_flags(context, + request->server, + client_princ, + flags)) { *status = "INVALID_S4U2SELF_REQUEST"; - return KRB5KDC_ERR_POLICY; + return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; /* match Windows error code */ } /* * Protocol transition is mutually exclusive with renew/forward/etc - * as well as user-to-user and constrained delegation. + * as well as user-to-user and constrained delegation. This check + * is also made in validate_as_request(). * * We can assert from this check that the header ticket was a TGT, as * that is validated previously in validate_tgs_request(). */ - if (request->kdc_options & (NO_TGT_OPTION | KDC_OPT_ENC_TKT_IN_SKEY | KDC_OPT_CNAME_IN_ADDL_TKT)) { + if (request->kdc_options & AS_INVALID_OPTIONS) { + *status = "INVALID AS OPTIONS"; return KRB5KDC_ERR_BADOPTION; } /* * Do not attempt to lookup principals in foreign realms. */ - if (is_local_principal((*for_user)->user)) { + if (is_local_principal((*s4u_x509_user)->user_id.user)) { + krb5_db_entry no_server; + *nprincs = 1; code = krb5_db_get_principal_ext(kdc_context, - (*for_user)->user, + (*s4u_x509_user)->user_id.user, KRB5_KDB_FLAG_INCLUDE_PAC, princ, nprincs, &more); if (code) { @@ -2021,14 +2230,15 @@ kdc_process_s4u2self_req(krb5_context context, return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; } - code = validate_s4u2self_request(request, princ, kdc_time, status); + memset(&no_server, 0, sizeof(no_server)); + + code = validate_as_request(request, *princ, + no_server, kdc_time, status); if (code) { return code; } } - *status = NULL; - return 0; } @@ -2049,7 +2259,7 @@ check_allowed_to_delegate_to(krb5_context context, /* Must be in same realm */ if (!krb5_realm_compare(context, server->princ, proxy)) { - return KRB5KDC_ERR_BADOPTION; + return KRB5KDC_ERR_POLICY; } req.server = server; @@ -2345,3 +2555,63 @@ log_tgs_alt_tgt(krb5_principal p) /* OpenSolaris: audit_krb5kdc_tgs_req_alt_tgt(...) */ } +krb5_boolean +enctype_requires_etype_info_2(krb5_enctype enctype) +{ + switch(enctype) { + case ENCTYPE_DES_CBC_CRC: + case ENCTYPE_DES_CBC_MD4: + case ENCTYPE_DES_CBC_MD5: + case ENCTYPE_DES3_CBC_SHA1: + case ENCTYPE_DES3_CBC_RAW: + case ENCTYPE_ARCFOUR_HMAC: + case ENCTYPE_ARCFOUR_HMAC_EXP : + return 0; + default: + return krb5_c_valid_enctype(enctype); + } +} + +/* XXX where are the generic helper routines for this? */ +krb5_error_code +add_pa_data_element(krb5_context context, + krb5_pa_data *padata, + krb5_pa_data ***inout_padata, + krb5_boolean copy) +{ + int i; + krb5_pa_data **p; + + if (*inout_padata != NULL) { + for (i = 0; (*inout_padata)[i] != NULL; i++) + ; + } else + i = 0; + + p = realloc(*inout_padata, (i + 2) * sizeof(krb5_pa_data *)); + if (p == NULL) + return ENOMEM; + + *inout_padata = p; + + p[i] = (krb5_pa_data *)malloc(sizeof(krb5_pa_data)); + if (p[i] == NULL) + return ENOMEM; + *(p[i]) = *padata; + + p[i + 1] = NULL; + + if (copy) { + p[i]->contents = (krb5_octet *)malloc(padata->length); + if (p[i]->contents == NULL) { + free(p[i]); + p[i] = NULL; + return ENOMEM; + } + + memcpy(p[i]->contents, padata->contents, padata->length); + } + + return 0; +} + diff --git a/src/kdc/kdc_util.h b/src/kdc/kdc_util.h index 060442604..cb8fb5f7a 100644 --- a/src/kdc/kdc_util.h +++ b/src/kdc/kdc_util.h @@ -150,6 +150,8 @@ int against_local_policy_tgs (krb5_kdc_req *, krb5_db_entry, krb5_ticket *, const char **); /* kdc_preauth.c */ +krb5_boolean enctype_requires_etype_info_2(krb5_enctype enctype); + const char * missing_required_preauth (krb5_db_entry *client, krb5_db_entry *server, krb5_enc_tkt_part *enc_tkt_reply); @@ -177,6 +179,12 @@ krb5_error_code free_padata_context krb5_pa_data *find_pa_data (krb5_pa_data **padata, krb5_preauthtype pa_type); +krb5_error_code add_pa_data_element + (krb5_context context, + krb5_pa_data *padata, + krb5_pa_data ***out_padata, + krb5_boolean copy); + /* kdc_authdata.c */ krb5_error_code load_authdata_plugins(krb5_context context); krb5_error_code unload_authdata_plugins(krb5_context context); @@ -239,13 +247,22 @@ krb5_error_code kdc_process_s4u2self_req krb5_kdc_req *request, krb5_const_principal client_princ, const krb5_db_entry *server, - krb5_keyblock *subkey, + krb5_keyblock *tgs_subkey, + krb5_keyblock *tgs_session, krb5_timestamp kdc_time, - krb5_pa_for_user **s4u2_req, + krb5_pa_s4u_x509_user **s4u2self_req, krb5_db_entry *princ, int *nprincs, const char **status); +krb5_error_code kdc_make_s4u2self_rep + (krb5_context context, + krb5_keyblock *tgs_subkey, + krb5_keyblock *tgs_session, + krb5_pa_s4u_x509_user *req_s4u_user, + krb5_kdc_rep *reply, + krb5_enc_kdc_rep_part *reply_encpart); + krb5_error_code kdc_process_s4u2proxy_req (krb5_context context, krb5_kdc_req *request, diff --git a/src/lib/crypto/krb/enc_provider/Makefile.in b/src/lib/crypto/krb/enc_provider/Makefile.in new file mode 100644 index 000000000..2eedf1d9d --- /dev/null +++ b/src/lib/crypto/krb/enc_provider/Makefile.in @@ -0,0 +1,41 @@ +thisconfigdir=../../../.. +myfulldir=lib/crypto/krb/enc_provider +mydir=lib/crypto/krb/enc_provider +BUILDTOP=$(REL)..$(S)..$(S)..$(S).. +LOCALINCLUDES = -I$(srcdir)/../../@CRYPTO_IMPL@/des -I$(srcdir)/../../@CRYPTO_IMPL@/arcfour \ + -I$(srcdir)/../../@CRYPTO_IMPL@/aes -I$(srcdir)/.. -I$(srcdir)/../../@CRYPTO_IMPL@ +DEFS= + +##DOS##BUILDTOP = ..\..\..\.. +##DOS##PREFIXDIR=enc_provider +##DOS##OBJFILE=..\$(OUTPRE)enc_prov.lst + +PROG_LIBPATH=-L$(TOPLIBD) +PROG_RPATH=$(KRB5_LIBDIR) + +STLIBOBJS= des.o des3.o rc4.o aes.o + +OBJS= \ + $(OUTPRE)des.$(OBJEXT) \ + $(OUTPRE)des3.$(OBJEXT) \ + $(OUTPRE)aes.$(OBJEXT) \ + $(OUTPRE)rc4.$(OBJEXT) + +SRCS= \ + $(srcdir)/des.c \ + $(srcdir)/des3.c \ + $(srcdir)/aes.c \ + $(srcdir)/rc4.c + +##DOS##LIBOBJS = $(OBJS) + +all-unix:: all-libobjs + +includes:: depend + +depend:: $(SRCS) + +clean-unix:: clean-libobjs + +@libobj_frag@ + diff --git a/src/lib/crypto/krb/enc_provider/aes.c b/src/lib/crypto/krb/enc_provider/aes.c new file mode 100644 index 000000000..060d119c4 --- /dev/null +++ b/src/lib/crypto/krb/enc_provider/aes.c @@ -0,0 +1,415 @@ +/* + * lib/crypto/enc_provider/aes.c + * + * Copyright (C) 2003, 2007, 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 "enc_provider.h" +#include "aes.h" +#include "../aead.h" + +#if 0 +aes_rval aes_blk_len(unsigned int blen, aes_ctx cx[1]); +aes_rval aes_enc_key(const unsigned char in_key[], unsigned int klen, aes_ctx cx[1]); +aes_rval aes_enc_blk(const unsigned char in_blk[], unsigned char out_blk[], const aes_ctx cx[1]); +aes_rval aes_dec_key(const unsigned char in_key[], unsigned int klen, aes_ctx cx[1]); +aes_rval aes_dec_blk(const unsigned char in_blk[], unsigned char out_blk[], const aes_ctx cx[1]); +#endif + +#define CHECK_SIZES 0 + +#if 0 +static void printd (const char *descr, krb5_data *d) { + int i, j; + const int r = 16; + + printf("%s:", descr); + + for (i = 0; i < d->length; i += r) { + printf("\n %04x: ", i); + for (j = i; j < i + r && j < d->length; j++) + printf(" %02x", 0xff & d->data[j]); +#ifdef SHOW_TEXT + for (; j < i + r; j++) + printf(" "); + printf(" "); + for (j = i; j < i + r && j < d->length; j++) { + int c = 0xff & d->data[j]; + printf("%c", isprint(c) ? c : '.'); + } +#endif + } + printf("\n"); +} +#endif + +static inline void enc(char *out, const char *in, aes_ctx *ctx) +{ + if (aes_enc_blk((const unsigned char *)in, (unsigned char *)out, ctx) + != aes_good) + abort(); +} +static inline void dec(char *out, const char *in, aes_ctx *ctx) +{ + if (aes_dec_blk((const unsigned char *)in, (unsigned char *)out, ctx) + != aes_good) + abort(); +} + +static void xorblock(char *out, const char *in) +{ + int z; + for (z = 0; z < BLOCK_SIZE; z++) + out[z] ^= in[z]; +} + +krb5_error_code +krb5int_aes_encrypt(const krb5_keyblock *key, const krb5_data *ivec, + const krb5_data *input, krb5_data *output) +{ + aes_ctx ctx; + char tmp[BLOCK_SIZE], tmp2[BLOCK_SIZE], tmp3[BLOCK_SIZE]; + int nblocks = 0, blockno; + +/* CHECK_SIZES; */ + + if (aes_enc_key(key->contents, key->length, &ctx) != aes_good) + abort(); + + if (ivec) + memcpy(tmp, ivec->data, BLOCK_SIZE); + else + memset(tmp, 0, BLOCK_SIZE); + + nblocks = (input->length + BLOCK_SIZE - 1) / BLOCK_SIZE; + + if (nblocks == 1) { + /* XXX Used for DK function. */ + enc(output->data, input->data, &ctx); + } else { + unsigned int nleft; + + for (blockno = 0; blockno < nblocks - 2; blockno++) { + xorblock(tmp, input->data + blockno * BLOCK_SIZE); + enc(tmp2, tmp, &ctx); + memcpy(output->data + blockno * BLOCK_SIZE, tmp2, BLOCK_SIZE); + + /* Set up for next block. */ + memcpy(tmp, tmp2, BLOCK_SIZE); + } + /* Do final CTS step for last two blocks (the second of which + may or may not be incomplete). */ + xorblock(tmp, input->data + (nblocks - 2) * BLOCK_SIZE); + enc(tmp2, tmp, &ctx); + nleft = input->length - (nblocks - 1) * BLOCK_SIZE; + memcpy(output->data + (nblocks - 1) * BLOCK_SIZE, tmp2, nleft); + memcpy(tmp, tmp2, BLOCK_SIZE); + + memset(tmp3, 0, sizeof(tmp3)); + memcpy(tmp3, input->data + (nblocks - 1) * BLOCK_SIZE, nleft); + xorblock(tmp, tmp3); + enc(tmp2, tmp, &ctx); + memcpy(output->data + (nblocks - 2) * BLOCK_SIZE, tmp2, BLOCK_SIZE); + if (ivec) + memcpy(ivec->data, tmp2, BLOCK_SIZE); + } + + return 0; +} + +krb5_error_code +krb5int_aes_decrypt(const krb5_keyblock *key, const krb5_data *ivec, + const krb5_data *input, krb5_data *output) +{ + aes_ctx ctx; + char tmp[BLOCK_SIZE], tmp2[BLOCK_SIZE], tmp3[BLOCK_SIZE]; + int nblocks = 0, blockno; + + CHECK_SIZES; + + if (aes_dec_key(key->contents, key->length, &ctx) != aes_good) + abort(); + + if (ivec) + memcpy(tmp, ivec->data, BLOCK_SIZE); + else + memset(tmp, 0, BLOCK_SIZE); + + nblocks = (input->length + BLOCK_SIZE - 1) / BLOCK_SIZE; + + if (nblocks == 1) { + if (input->length < BLOCK_SIZE) + abort(); + dec(output->data, input->data, &ctx); + } else { + + for (blockno = 0; blockno < nblocks - 2; blockno++) { + dec(tmp2, input->data + blockno * BLOCK_SIZE, &ctx); + xorblock(tmp2, tmp); + memcpy(output->data + blockno * BLOCK_SIZE, tmp2, BLOCK_SIZE); + memcpy(tmp, input->data + blockno * BLOCK_SIZE, BLOCK_SIZE); + } + /* Do last two blocks, the second of which (next-to-last block + of plaintext) may be incomplete. */ + dec(tmp2, input->data + (nblocks - 2) * BLOCK_SIZE, &ctx); + /* Set tmp3 to last ciphertext block, padded. */ + memset(tmp3, 0, sizeof(tmp3)); + memcpy(tmp3, input->data + (nblocks - 1) * BLOCK_SIZE, + input->length - (nblocks - 1) * BLOCK_SIZE); + /* Set tmp2 to last (possibly partial) plaintext block, and + save it. */ + xorblock(tmp2, tmp3); + memcpy(output->data + (nblocks - 1) * BLOCK_SIZE, tmp2, + input->length - (nblocks - 1) * BLOCK_SIZE); + /* Maybe keep the trailing part, and copy in the last + ciphertext block. */ + memcpy(tmp2, tmp3, input->length - (nblocks - 1) * BLOCK_SIZE); + /* Decrypt, to get next to last plaintext block xor previous + ciphertext. */ + dec(tmp3, tmp2, &ctx); + xorblock(tmp3, tmp); + memcpy(output->data + (nblocks - 2) * BLOCK_SIZE, tmp3, BLOCK_SIZE); + if (ivec) + memcpy(ivec->data, input->data + (nblocks - 2) * BLOCK_SIZE, + BLOCK_SIZE); + } + + return 0; +} + +static krb5_error_code +krb5int_aes_encrypt_iov(const krb5_keyblock *key, + const krb5_data *ivec, + krb5_crypto_iov *data, + size_t num_data) +{ + aes_ctx ctx; + char tmp[BLOCK_SIZE], tmp2[BLOCK_SIZE]; + int nblocks = 0, blockno; + size_t input_length, i; + + if (aes_enc_key(key->contents, key->length, &ctx) != aes_good) + abort(); + + if (ivec != NULL) + memcpy(tmp, ivec->data, BLOCK_SIZE); + else + memset(tmp, 0, BLOCK_SIZE); + + for (i = 0, input_length = 0; i < num_data; i++) { + krb5_crypto_iov *iov = &data[i]; + + if (ENCRYPT_IOV(iov)) + input_length += iov->data.length; + } + + nblocks = (input_length + BLOCK_SIZE - 1) / BLOCK_SIZE; + + assert(nblocks > 1); + + { + char blockN2[BLOCK_SIZE]; /* second last */ + char blockN1[BLOCK_SIZE]; /* last block */ + struct iov_block_state input_pos, output_pos; + + IOV_BLOCK_STATE_INIT(&input_pos); + IOV_BLOCK_STATE_INIT(&output_pos); + + for (blockno = 0; blockno < nblocks - 2; blockno++) { + char blockN[BLOCK_SIZE]; + + krb5int_c_iov_get_block((unsigned char *)blockN, BLOCK_SIZE, data, num_data, &input_pos); + xorblock(tmp, blockN); + enc(tmp2, tmp, &ctx); + krb5int_c_iov_put_block(data, num_data, (unsigned char *)tmp2, BLOCK_SIZE, &output_pos); + + /* Set up for next block. */ + memcpy(tmp, tmp2, BLOCK_SIZE); + } + + /* Do final CTS step for last two blocks (the second of which + may or may not be incomplete). */ + + /* First, get the last two blocks */ + memset(blockN1, 0, sizeof(blockN1)); /* pad last block with zeros */ + krb5int_c_iov_get_block((unsigned char *)blockN2, BLOCK_SIZE, data, num_data, &input_pos); + krb5int_c_iov_get_block((unsigned char *)blockN1, BLOCK_SIZE, data, num_data, &input_pos); + + /* Encrypt second last block */ + xorblock(tmp, blockN2); + enc(tmp2, tmp, &ctx); + memcpy(blockN2, tmp2, BLOCK_SIZE); /* blockN2 now contains first block */ + memcpy(tmp, tmp2, BLOCK_SIZE); + + /* Encrypt last block */ + xorblock(tmp, blockN1); + enc(tmp2, tmp, &ctx); + memcpy(blockN1, tmp2, BLOCK_SIZE); + + /* Put the last two blocks back into the iovec (reverse order) */ + krb5int_c_iov_put_block(data, num_data, (unsigned char *)blockN1, BLOCK_SIZE, &output_pos); + krb5int_c_iov_put_block(data, num_data, (unsigned char *)blockN2, BLOCK_SIZE, &output_pos); + + if (ivec != NULL) + memcpy(ivec->data, blockN1, BLOCK_SIZE); + } + + return 0; +} + +static krb5_error_code +krb5int_aes_decrypt_iov(const krb5_keyblock *key, + const krb5_data *ivec, + krb5_crypto_iov *data, + size_t num_data) +{ + aes_ctx ctx; + char tmp[BLOCK_SIZE], tmp2[BLOCK_SIZE], tmp3[BLOCK_SIZE]; + int nblocks = 0, blockno; + unsigned int i; + size_t input_length; + + CHECK_SIZES; + + if (aes_dec_key(key->contents, key->length, &ctx) != aes_good) + abort(); + + if (ivec != NULL) + memcpy(tmp, ivec->data, BLOCK_SIZE); + else + memset(tmp, 0, BLOCK_SIZE); + + for (i = 0, input_length = 0; i < num_data; i++) { + krb5_crypto_iov *iov = &data[i]; + + if (ENCRYPT_IOV(iov)) + input_length += iov->data.length; + } + + nblocks = (input_length + BLOCK_SIZE - 1) / BLOCK_SIZE; + + assert(nblocks > 1); + + { + char blockN2[BLOCK_SIZE]; /* second last */ + char blockN1[BLOCK_SIZE]; /* last block */ + struct iov_block_state input_pos, output_pos; + + IOV_BLOCK_STATE_INIT(&input_pos); + IOV_BLOCK_STATE_INIT(&output_pos); + + for (blockno = 0; blockno < nblocks - 2; blockno++) { + char blockN[BLOCK_SIZE]; + + krb5int_c_iov_get_block((unsigned char *)blockN, BLOCK_SIZE, data, num_data, &input_pos); + dec(tmp2, blockN, &ctx); + xorblock(tmp2, tmp); + krb5int_c_iov_put_block(data, num_data, (unsigned char *)tmp2, BLOCK_SIZE, &output_pos); + memcpy(tmp, blockN, BLOCK_SIZE); + } + + /* Do last two blocks, the second of which (next-to-last block + of plaintext) may be incomplete. */ + + /* First, get the last two encrypted blocks */ + memset(blockN1, 0, sizeof(blockN1)); /* pad last block with zeros */ + krb5int_c_iov_get_block((unsigned char *)blockN2, BLOCK_SIZE, data, num_data, &input_pos); + krb5int_c_iov_get_block((unsigned char *)blockN1, BLOCK_SIZE, data, num_data, &input_pos); + + /* Decrypt second last block */ + dec(tmp2, blockN2, &ctx); + /* Set tmp2 to last (possibly partial) plaintext block, and + save it. */ + xorblock(tmp2, blockN1); + memcpy(blockN2, tmp2, BLOCK_SIZE); + + /* Maybe keep the trailing part, and copy in the last + ciphertext block. */ + input_length %= BLOCK_SIZE; + memcpy(tmp2, blockN1, input_length ? input_length : BLOCK_SIZE); + dec(tmp3, tmp2, &ctx); + xorblock(tmp3, tmp); + /* Copy out ivec first before we clobber blockN1 with plaintext */ + if (ivec != NULL) + memcpy(ivec->data, blockN1, BLOCK_SIZE); + memcpy(blockN1, tmp3, BLOCK_SIZE); + + /* Put the last two blocks back into the iovec */ + krb5int_c_iov_put_block(data, num_data, (unsigned char *)blockN1, BLOCK_SIZE, &output_pos); + krb5int_c_iov_put_block(data, num_data, (unsigned char *)blockN2, BLOCK_SIZE, &output_pos); + } + + return 0; +} + +static krb5_error_code +k5_aes_make_key(const krb5_data *randombits, krb5_keyblock *key) +{ + if (key->length != 16 && key->length != 32) + return(KRB5_BAD_KEYSIZE); + if (randombits->length != key->length) + return(KRB5_CRYPTO_INTERNAL); + + key->magic = KV5M_KEYBLOCK; + + memcpy(key->contents, randombits->data, randombits->length); + return(0); +} + +static krb5_error_code +krb5int_aes_init_state (const krb5_keyblock *key, krb5_keyusage usage, + krb5_data *state) +{ + state->length = 16; + state->data = (void *) malloc(16); + if (state->data == NULL) + return ENOMEM; + memset(state->data, 0, state->length); + return 0; +} + +const struct krb5_enc_provider krb5int_enc_aes128 = { + 16, + 16, 16, + krb5int_aes_encrypt, + krb5int_aes_decrypt, + k5_aes_make_key, + krb5int_aes_init_state, + krb5int_default_free_state, + krb5int_aes_encrypt_iov, + krb5int_aes_decrypt_iov +}; + +const struct krb5_enc_provider krb5int_enc_aes256 = { + 16, + 32, 32, + krb5int_aes_encrypt, + krb5int_aes_decrypt, + k5_aes_make_key, + krb5int_aes_init_state, + krb5int_default_free_state, + krb5int_aes_encrypt_iov, + krb5int_aes_decrypt_iov +}; + diff --git a/src/lib/crypto/krb/enc_provider/deps b/src/lib/crypto/krb/enc_provider/deps new file mode 100644 index 000000000..064976279 --- /dev/null +++ b/src/lib/crypto/krb/enc_provider/deps @@ -0,0 +1,49 @@ +# +# Generated makefile dependencies follow. +# +des.so des.po $(OUTPRE)des.$(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/krb5.h \ + $(SRCTOP)/include/krb5/locate_plugin.h $(SRCTOP)/include/krb5/preauth_plugin.h \ + $(SRCTOP)/include/port-sockets.h $(SRCTOP)/include/socket-utils.h \ + $(srcdir)/../../builtin/des/des_int.h $(srcdir)/../aead.h \ + $(srcdir)/../cksumtypes.h des.c enc_provider.h +des3.so des3.po $(OUTPRE)des3.$(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/krb5.h \ + $(SRCTOP)/include/krb5/locate_plugin.h $(SRCTOP)/include/krb5/preauth_plugin.h \ + $(SRCTOP)/include/port-sockets.h $(SRCTOP)/include/socket-utils.h \ + $(srcdir)/../../builtin/des/des_int.h $(srcdir)/../aead.h \ + $(srcdir)/../cksumtypes.h des3.c +aes.so aes.po $(OUTPRE)aes.$(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/krb5.h \ + $(SRCTOP)/include/krb5/locate_plugin.h $(SRCTOP)/include/krb5/preauth_plugin.h \ + $(SRCTOP)/include/port-sockets.h $(SRCTOP)/include/socket-utils.h \ + $(srcdir)/../../builtin/aes/aes.h $(srcdir)/../../builtin/aes/uitypes.h \ + $(srcdir)/../aead.h $(srcdir)/../cksumtypes.h aes.c \ + enc_provider.h +rc4.so rc4.po $(OUTPRE)rc4.$(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/krb5.h \ + $(SRCTOP)/include/krb5/locate_plugin.h $(SRCTOP)/include/krb5/preauth_plugin.h \ + $(SRCTOP)/include/port-sockets.h $(SRCTOP)/include/socket-utils.h \ + $(srcdir)/../../builtin/arcfour/arcfour-int.h $(srcdir)/../../builtin/arcfour/arcfour.h \ + $(srcdir)/../aead.h $(srcdir)/../cksumtypes.h enc_provider.h \ + rc4.c diff --git a/src/lib/crypto/krb/enc_provider/des.c b/src/lib/crypto/krb/enc_provider/des.c new file mode 100644 index 000000000..547f6b976 --- /dev/null +++ b/src/lib/crypto/krb/enc_provider/des.c @@ -0,0 +1,181 @@ +/* + * Copyright (C) 1998 by the FundsXpress, INC. + * + * 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 FundsXpress. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. FundsXpress makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "k5-int.h" +#include "des_int.h" +#include "enc_provider.h" +#include "aead.h" + +static krb5_error_code +k5_des_docrypt(const krb5_keyblock *key, const krb5_data *ivec, + const krb5_data *input, krb5_data *output, int enc) +{ + mit_des_key_schedule schedule; + + /* key->enctype was checked by the caller */ + + if (key->length != 8) + return(KRB5_BAD_KEYSIZE); + if ((input->length%8) != 0) + return(KRB5_BAD_MSIZE); + if (ivec && (ivec->length != 8)) + return(KRB5_BAD_MSIZE); + if (input->length != output->length) + return(KRB5_BAD_MSIZE); + + switch (mit_des_key_sched(key->contents, schedule)) { + case -1: + return(KRB5DES_BAD_KEYPAR); + case -2: + return(KRB5DES_WEAK_KEY); + } + + /* this has a return value, but the code always returns zero */ + + mit_des_cbc_encrypt((krb5_pointer) input->data, + (krb5_pointer) output->data, input->length, + schedule, + (ivec + ? (const unsigned char *) ivec->data + : (const unsigned char *) mit_des_zeroblock), + enc); + + memset(schedule, 0, sizeof(schedule)); + + return(0); +} + +static krb5_error_code +k5_des_encrypt(const krb5_keyblock *key, const krb5_data *ivec, + const krb5_data *input, krb5_data *output) +{ + return(k5_des_docrypt(key, ivec, input, output, 1)); +} + +static krb5_error_code +k5_des_decrypt(const krb5_keyblock *key, const krb5_data *ivec, + const krb5_data *input, krb5_data *output) +{ + return(k5_des_docrypt(key, ivec, input, output, 0)); +} + +static krb5_error_code +k5_des_make_key(const krb5_data *randombits, krb5_keyblock *key) +{ + if (key->length != 8) + return(KRB5_BAD_KEYSIZE); + if (randombits->length != 7) + return(KRB5_CRYPTO_INTERNAL); + + key->magic = KV5M_KEYBLOCK; + key->length = 8; + + /* take the seven bytes, move them around into the top 7 bits of the + 8 key bytes, then compute the parity bits */ + + memcpy(key->contents, randombits->data, randombits->length); + key->contents[7] = (((key->contents[0]&1)<<1) | ((key->contents[1]&1)<<2) | + ((key->contents[2]&1)<<3) | ((key->contents[3]&1)<<4) | + ((key->contents[4]&1)<<5) | ((key->contents[5]&1)<<6) | + ((key->contents[6]&1)<<7)); + + mit_des_fixup_key_parity(key->contents); + + return(0); +} + +static krb5_error_code +k5_des_docrypt_iov(const krb5_keyblock *key, const krb5_data *ivec, + krb5_crypto_iov *data, size_t num_data, int enc) +{ + mit_des_key_schedule schedule; + size_t input_length = 0; + unsigned int i; + + /* key->enctype was checked by the caller */ + + if (key->length != 8) + return(KRB5_BAD_KEYSIZE); + + for (i = 0; i < num_data; i++) { + const krb5_crypto_iov *iov = &data[i]; + + if (ENCRYPT_DATA_IOV(iov)) + input_length += iov->data.length; + } + + if ((input_length % 8) != 0) + return(KRB5_BAD_MSIZE); + if (ivec && (ivec->length != 8)) + return(KRB5_BAD_MSIZE); + + switch (mit_des_key_sched(key->contents, schedule)) { + case -1: + return(KRB5DES_BAD_KEYPAR); + case -2: + return(KRB5DES_WEAK_KEY); + } + + /* this has a return value, but the code always returns zero */ + if (enc) + krb5int_des_cbc_encrypt_iov(data, num_data, schedule, ivec ? ivec->data : NULL); + else + krb5int_des_cbc_decrypt_iov(data, num_data, schedule, ivec ? ivec->data : NULL); + + memset(schedule, 0, sizeof(schedule)); + + return(0); +} + +static krb5_error_code +k5_des_encrypt_iov(const krb5_keyblock *key, + const krb5_data *ivec, + krb5_crypto_iov *data, + size_t num_data) +{ + return k5_des_docrypt_iov(key, ivec, data, num_data, 1); +} + +static krb5_error_code +k5_des_decrypt_iov(const krb5_keyblock *key, + const krb5_data *ivec, + krb5_crypto_iov *data, + size_t num_data) +{ + return k5_des_docrypt_iov(key, ivec, data, num_data, 0); +} + +const struct krb5_enc_provider krb5int_enc_des = { + 8, + 7, 8, + k5_des_encrypt, + k5_des_decrypt, + k5_des_make_key, + krb5int_des_init_state, + krb5int_default_free_state, + k5_des_encrypt_iov, + k5_des_decrypt_iov +}; diff --git a/src/lib/crypto/krb/enc_provider/des3.c b/src/lib/crypto/krb/enc_provider/des3.c new file mode 100644 index 000000000..412c994a7 --- /dev/null +++ b/src/lib/crypto/krb/enc_provider/des3.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) 1998 by the FundsXpress, INC. + * + * 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 FundsXpress. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. FundsXpress makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "k5-int.h" +#include "des_int.h" +#include "../aead.h" + +static krb5_error_code +validate_and_schedule(const krb5_keyblock *key, const krb5_data *ivec, + const krb5_data *input, const krb5_data *output, + mit_des3_key_schedule *schedule) +{ + /* key->enctype was checked by the caller */ + + if (key->length != 24) + return(KRB5_BAD_KEYSIZE); + if ((input->length%8) != 0) + return(KRB5_BAD_MSIZE); + if (ivec && (ivec->length != 8)) + return(KRB5_BAD_MSIZE); + if (input->length != output->length) + return(KRB5_BAD_MSIZE); + + switch (mit_des3_key_sched(*(mit_des3_cblock *)key->contents, + *schedule)) { + case -1: + return(KRB5DES_BAD_KEYPAR); + case -2: + return(KRB5DES_WEAK_KEY); + } + return 0; +} + +static krb5_error_code +validate_and_schedule_iov(const krb5_keyblock *key, const krb5_data *ivec, + const krb5_crypto_iov *data, size_t num_data, + mit_des3_key_schedule *schedule) +{ + size_t i, input_length; + + for (i = 0, input_length = 0; i < num_data; i++) { + const krb5_crypto_iov *iov = &data[i]; + + if (ENCRYPT_IOV(iov)) + input_length += iov->data.length; + } + + if (key->length != 24) + return(KRB5_BAD_KEYSIZE); + if ((input_length%8) != 0) + return(KRB5_BAD_MSIZE); + if (ivec && (ivec->length != 8)) + return(KRB5_BAD_MSIZE); + + switch (mit_des3_key_sched(*(mit_des3_cblock *)key->contents, + *schedule)) { + case -1: + return(KRB5DES_BAD_KEYPAR); + case -2: + return(KRB5DES_WEAK_KEY); + } + return 0; +} + +static krb5_error_code +k5_des3_encrypt(const krb5_keyblock *key, const krb5_data *ivec, + const krb5_data *input, krb5_data *output) +{ + mit_des3_key_schedule schedule; + krb5_error_code err; + + err = validate_and_schedule(key, ivec, input, output, &schedule); + if (err) + return err; + + /* this has a return value, but the code always returns zero */ + krb5int_des3_cbc_encrypt((krb5_pointer) input->data, + (krb5_pointer) output->data, input->length, + schedule[0], schedule[1], schedule[2], + ivec?(const unsigned char *) ivec->data:(const unsigned char *)mit_des_zeroblock); + + zap(schedule, sizeof(schedule)); + + return(0); +} + +static krb5_error_code +k5_des3_decrypt(const krb5_keyblock *key, const krb5_data *ivec, + const krb5_data *input, krb5_data *output) +{ + mit_des3_key_schedule schedule; + krb5_error_code err; + + err = validate_and_schedule(key, ivec, input, output, &schedule); + if (err) + return err; + + /* this has a return value, but the code always returns zero */ + krb5int_des3_cbc_decrypt((krb5_pointer) input->data, + (krb5_pointer) output->data, input->length, + schedule[0], schedule[1], schedule[2], + ivec?(const unsigned char *) ivec->data:(const unsigned char *)mit_des_zeroblock); + + zap(schedule, sizeof(schedule)); + + return(0); +} + +static krb5_error_code +k5_des3_make_key(const krb5_data *randombits, krb5_keyblock *key) +{ + int i; + + if (key->length != 24) + return(KRB5_BAD_KEYSIZE); + if (randombits->length != 21) + return(KRB5_CRYPTO_INTERNAL); + + key->magic = KV5M_KEYBLOCK; + key->length = 24; + + /* take the seven bytes, move them around into the top 7 bits of the + 8 key bytes, then compute the parity bits. Do this three times. */ + + for (i=0; i<3; i++) { + memcpy(key->contents+i*8, randombits->data+i*7, 7); + key->contents[i*8+7] = (((key->contents[i*8]&1)<<1) | + ((key->contents[i*8+1]&1)<<2) | + ((key->contents[i*8+2]&1)<<3) | + ((key->contents[i*8+3]&1)<<4) | + ((key->contents[i*8+4]&1)<<5) | + ((key->contents[i*8+5]&1)<<6) | + ((key->contents[i*8+6]&1)<<7)); + + mit_des_fixup_key_parity(key->contents+i*8); + } + + return(0); +} + +static krb5_error_code +k5_des3_encrypt_iov(const krb5_keyblock *key, + const krb5_data *ivec, + krb5_crypto_iov *data, + size_t num_data) +{ + mit_des3_key_schedule schedule; + krb5_error_code err; + + err = validate_and_schedule_iov(key, ivec, data, num_data, &schedule); + if (err) + return err; + + /* this has a return value, but the code always returns zero */ + krb5int_des3_cbc_encrypt_iov(data, num_data, + schedule[0], schedule[1], schedule[2], + ivec != NULL ? (unsigned char *) ivec->data : NULL); + + zap(schedule, sizeof(schedule)); + + return(0); +} + +static krb5_error_code +k5_des3_decrypt_iov(const krb5_keyblock *key, + const krb5_data *ivec, + krb5_crypto_iov *data, + size_t num_data) +{ + mit_des3_key_schedule schedule; + krb5_error_code err; + + err = validate_and_schedule_iov(key, ivec, data, num_data, &schedule); + if (err) + return err; + + /* this has a return value, but the code always returns zero */ + krb5int_des3_cbc_decrypt_iov(data, num_data, + schedule[0], schedule[1], schedule[2], + ivec != NULL ? (unsigned char *) ivec->data : NULL); + + zap(schedule, sizeof(schedule)); + + return(0); +} + +const struct krb5_enc_provider krb5int_enc_des3 = { + 8, + 21, 24, + k5_des3_encrypt, + k5_des3_decrypt, + k5_des3_make_key, + krb5int_des_init_state, + krb5int_default_free_state, + k5_des3_encrypt_iov, + k5_des3_decrypt_iov +}; + diff --git a/src/lib/crypto/krb/enc_provider/enc_provider.h b/src/lib/crypto/krb/enc_provider/enc_provider.h new file mode 100644 index 000000000..92022b3c8 --- /dev/null +++ b/src/lib/crypto/krb/enc_provider/enc_provider.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 1998 by the FundsXpress, INC. + * + * 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 FundsXpress. not be used in advertising or publicity pertaining + * to distribution of the software without specific, written prior + * permission. FundsXpress makes no representations about the suitability of + * this software for any purpose. It is provided "as is" without express + * or implied warranty. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#include "k5-int.h" + +extern const struct krb5_enc_provider krb5int_enc_des; +extern const struct krb5_enc_provider krb5int_enc_des3; +extern const struct krb5_enc_provider krb5int_enc_arcfour; +extern const struct krb5_enc_provider krb5int_enc_aes128; +extern const struct krb5_enc_provider krb5int_enc_aes256; +extern const struct krb5_enc_provider krb5int_enc_aes128_ctr; +extern const struct krb5_enc_provider krb5int_enc_aes256_ctr; + diff --git a/src/lib/crypto/krb/enc_provider/rc4.c b/src/lib/crypto/krb/enc_provider/rc4.c new file mode 100644 index 000000000..b950a605b --- /dev/null +++ b/src/lib/crypto/krb/enc_provider/rc4.c @@ -0,0 +1,271 @@ +/* arcfour.c + * + * Copyright (c) 2000 by Computer Science Laboratory, + * Rensselaer Polytechnic Institute + * + * #include STD_DISCLAIMER + */ + +#include "k5-int.h" +#include "arcfour-int.h" +#include "enc_provider.h" +#include "../aead.h" +/* gets the next byte from the PRNG */ +#if ((__GNUC__ >= 2) ) +static __inline__ unsigned int k5_arcfour_byte(ArcfourContext *); +#else +static unsigned int k5_arcfour_byte(ArcfourContext *); +#endif /* gcc inlines*/ + +/* Initializes the context and sets the key. */ +static krb5_error_code k5_arcfour_init(ArcfourContext *ctx, const unsigned char *key, + unsigned int keylen); + +/* Encrypts/decrypts data. */ +static void k5_arcfour_crypt(ArcfourContext *ctx, unsigned char *dest, + const unsigned char *src, unsigned int len); + +/* Interface layer to kerb5 crypto layer */ +static krb5_error_code +k5_arcfour_docrypt(const krb5_keyblock *, const krb5_data *, + const krb5_data *, krb5_data *); + +/* from a random bitstrem, construct a key */ +static krb5_error_code +k5_arcfour_make_key(const krb5_data *, krb5_keyblock *); + +static const unsigned char arcfour_weakkey1[] = {0x00, 0x00, 0xfd}; +static const unsigned char arcfour_weakkey2[] = {0x03, 0xfd, 0xfc}; +static const struct { + size_t length; + const unsigned char *data; +} arcfour_weakkeys[] = { + { sizeof (arcfour_weakkey1), arcfour_weakkey1}, + { sizeof (arcfour_weakkey2), arcfour_weakkey2}, +}; + +static inline unsigned int k5_arcfour_byte(ArcfourContext * ctx) +{ + unsigned int x; + unsigned int y; + unsigned int sx, sy; + unsigned char *state; + + state = ctx->state; + x = (ctx->x + 1) & 0xff; + sx = state[x]; + y = (sx + ctx->y) & 0xff; + sy = state[y]; + ctx->x = x; + ctx->y = y; + state[y] = sx; + state[x] = sy; + return state[(sx + sy) & 0xff]; +} + +static void k5_arcfour_crypt(ArcfourContext *ctx, unsigned char *dest, + const unsigned char *src, unsigned int len) +{ + unsigned int i; + for (i = 0; i < len; i++) + dest[i] = src[i] ^ k5_arcfour_byte(ctx); +} + + +static krb5_error_code +k5_arcfour_init(ArcfourContext *ctx, const unsigned char *key, + unsigned int key_len) +{ + unsigned int t, u; + unsigned int keyindex; + unsigned int stateindex; + unsigned char* state; + unsigned int counter; + + if (key_len != 16) + return KRB5_BAD_MSIZE; /*this is probably not the correct error code + to return */ + for (counter=0; + counter < sizeof(arcfour_weakkeys)/sizeof(arcfour_weakkeys[0]); + counter++) + if (!memcmp(key, arcfour_weakkeys[counter].data, + arcfour_weakkeys[counter].length)) + return KRB5DES_WEAK_KEY; /* most certainly not the correct error */ + + state = &ctx->state[0]; + ctx->x = 0; + ctx->y = 0; + for (counter = 0; counter < 256; counter++) + state[counter] = counter; + keyindex = 0; + stateindex = 0; + for (counter = 0; counter < 256; counter++) + { + t = state[counter]; + stateindex = (stateindex + key[keyindex] + t) & 0xff; + u = state[stateindex]; + state[stateindex] = t; + state[counter] = u; + if (++keyindex >= key_len) + keyindex = 0; + } + return 0; +} + + +/* The workhorse of the arcfour system, this impliments the cipher */ +static krb5_error_code +k5_arcfour_docrypt(const krb5_keyblock *key, const krb5_data *state, + const krb5_data *input, krb5_data *output) +{ + ArcfourContext *arcfour_ctx; + ArcFourCipherState *cipher_state; + int ret; + + if (key->length != 16) + return(KRB5_BAD_KEYSIZE); + if (state && (state->length != sizeof (ArcFourCipherState))) + return(KRB5_BAD_MSIZE); + if (input->length != output->length) + return(KRB5_BAD_MSIZE); + + if (state) { + cipher_state = (ArcFourCipherState *) state->data; + arcfour_ctx=&cipher_state->ctx; + if (cipher_state->initialized == 0) { + if ((ret=k5_arcfour_init(arcfour_ctx, key->contents, key->length))) { + return ret; + } + cipher_state->initialized = 1; + } + k5_arcfour_crypt(arcfour_ctx, (unsigned char *) output->data, (const unsigned char *) input->data, input->length); + } + else { + arcfour_ctx=malloc(sizeof (ArcfourContext)); + if (arcfour_ctx == NULL) + return ENOMEM; + if ((ret=k5_arcfour_init(arcfour_ctx, key->contents, key->length))) { + free(arcfour_ctx); + return (ret); + } + k5_arcfour_crypt(arcfour_ctx, (unsigned char * ) output->data, + (const unsigned char * ) input->data, input->length); + memset(arcfour_ctx, 0, sizeof (ArcfourContext)); + free(arcfour_ctx); + } + + return 0; +} + +/* In-place encryption */ +static krb5_error_code +k5_arcfour_docrypt_iov(const krb5_keyblock *key, + const krb5_data *state, + krb5_crypto_iov *data, + size_t num_data) +{ + ArcfourContext *arcfour_ctx = NULL; + ArcFourCipherState *cipher_state = NULL; + krb5_error_code ret; + size_t i; + + if (key->length != 16) + return KRB5_BAD_KEYSIZE; + if (state != NULL && (state->length != sizeof(ArcFourCipherState))) + return KRB5_BAD_MSIZE; + + if (state != NULL) { + cipher_state = (ArcFourCipherState *)state->data; + arcfour_ctx = &cipher_state->ctx; + if (cipher_state->initialized == 0) { + ret = k5_arcfour_init(arcfour_ctx, key->contents, key->length); + if (ret != 0) + return ret; + + cipher_state->initialized = 1; + } + } else { + arcfour_ctx = (ArcfourContext *)malloc(sizeof(ArcfourContext)); + if (arcfour_ctx == NULL) + return ENOMEM; + + ret = k5_arcfour_init(arcfour_ctx, key->contents, key->length); + if (ret != 0) { + free(arcfour_ctx); + return ret; + } + } + + for (i = 0; i < num_data; i++) { + krb5_crypto_iov *iov = &data[i]; + + if (ENCRYPT_IOV(iov)) + k5_arcfour_crypt(arcfour_ctx, (unsigned char *)iov->data.data, + (const unsigned char *)iov->data.data, iov->data.length); + } + + if (state == NULL) { + memset(arcfour_ctx, 0, sizeof(ArcfourContext)); + free(arcfour_ctx); + } + + return 0; +} + +static krb5_error_code +k5_arcfour_make_key(const krb5_data *randombits, krb5_keyblock *key) +{ + if (key->length != 16) + return(KRB5_BAD_KEYSIZE); + if (randombits->length != 16) + return(KRB5_CRYPTO_INTERNAL); + + key->magic = KV5M_KEYBLOCK; + key->length = 16; + + memcpy(key->contents, randombits->data, randombits->length); + + return(0); +} + +static krb5_error_code +k5_arcfour_init_state (const krb5_keyblock *key, + krb5_keyusage keyusage, krb5_data *new_state) +{ + /* Note that we can't actually set up the state here because the key + * will change between now and when encrypt is called + * because it is data dependent. Yeah, this has strange + * properties. --SDH + */ + new_state->length = sizeof (ArcFourCipherState); + new_state->data = malloc (new_state->length); + if (new_state->data) { + memset (new_state->data, 0 , new_state->length); + /* That will set initialized to zero*/ + }else { + return (ENOMEM); + } + return 0; +} + +/* Since the arcfour cipher is identical going forwards and backwards, + we just call "docrypt" directly +*/ +const struct krb5_enc_provider krb5int_enc_arcfour = { + /* This seems to work... although I am not sure what the + implications are in other places in the kerberos library */ + 1, + /* Keysize is arbitrary in arcfour, but the constraints of the + system, and to attempt to work with the MSFT system forces us + to 16byte/128bit. Since there is no parity in the key, the + byte and length are the same. */ + 16, 16, + k5_arcfour_docrypt, + k5_arcfour_docrypt, + k5_arcfour_make_key, + k5_arcfour_init_state, /*xxx not implemented yet*/ + krb5int_default_free_state, + k5_arcfour_docrypt_iov, + k5_arcfour_docrypt_iov +}; + diff --git a/src/lib/gssapi/generic/gssapi_ext.h b/src/lib/gssapi/generic/gssapi_ext.h index 40f5ab809..ce115639b 100644 --- a/src/lib/gssapi/generic/gssapi_ext.h +++ b/src/lib/gssapi/generic/gssapi_ext.h @@ -254,6 +254,37 @@ OM_uint32 KRB5_CALLCONV gss_release_iov_buffer gss_iov_buffer_desc *, /* iov */ int); /* iov_count */ + +/* + * Protocol transition + */ +OM_uint32 KRB5_CALLCONV +gss_acquire_cred_impersonate_name( + OM_uint32 *, /* minor_status */ + const gss_cred_id_t, /* impersonator_cred_handle */ + const gss_name_t, /* desired_name */ + OM_uint32, /* time_req */ + const gss_OID_set, /* desired_mechs */ + gss_cred_usage_t, /* cred_usage */ + gss_cred_id_t *, /* output_cred_handle */ + gss_OID_set *, /* actual_mechs */ + OM_uint32 *); /* time_rec */ + +OM_uint32 KRB5_CALLCONV +gss_add_cred_impersonate_name( + OM_uint32 *, /* minor_status */ + gss_cred_id_t, /* input_cred_handle */ + const gss_cred_id_t, /* impersonator_cred_handle */ + const gss_name_t, /* desired_name */ + const gss_OID, /* desired_mech */ + gss_cred_usage_t, /* cred_usage */ + OM_uint32, /* initiator_time_req */ + OM_uint32, /* acceptor_time_req */ + gss_cred_id_t *, /* output_cred_handle */ + gss_OID_set *, /* actual_mechs */ + OM_uint32 *, /* initiator_time_rec */ + OM_uint32 *); /* acceptor_time_rec */ + #ifdef __cplusplus } #endif diff --git a/src/lib/gssapi/krb5/Makefile.in b/src/lib/gssapi/krb5/Makefile.in index 2ee9e1d9c..645b91b11 100644 --- a/src/lib/gssapi/krb5/Makefile.in +++ b/src/lib/gssapi/krb5/Makefile.in @@ -73,6 +73,7 @@ SRCS = \ $(srcdir)/rel_cred.c \ $(srcdir)/rel_oid.c \ $(srcdir)/rel_name.c \ + $(srcdir)/s4u_gss_glue.c \ $(srcdir)/seal.c \ $(srcdir)/set_allowable_enctypes.c \ $(srcdir)/ser_sctx.c \ @@ -123,6 +124,7 @@ OBJS = \ $(OUTPRE)rel_cred.$(OBJEXT) \ $(OUTPRE)rel_oid.$(OBJEXT) \ $(OUTPRE)rel_name.$(OBJEXT) \ + $(OUTPRE)s4u_gss_glue.$(OBJEXT) \ $(OUTPRE)seal.$(OBJEXT) \ $(OUTPRE)set_allowable_enctypes.$(OBJEXT) \ $(OUTPRE)ser_sctx.$(OBJEXT) \ @@ -176,6 +178,7 @@ STLIBOBJS = \ rel_cred.o \ rel_oid.o \ rel_name.o \ + s4u_gss_glue.o \ seal.o \ set_allowable_enctypes.o \ ser_sctx.o \ diff --git a/src/lib/gssapi/krb5/accept_sec_context.c b/src/lib/gssapi/krb5/accept_sec_context.c index dd17c044b..d340db7e7 100644 --- a/src/lib/gssapi/krb5/accept_sec_context.c +++ b/src/lib/gssapi/krb5/accept_sec_context.c @@ -114,6 +114,53 @@ #ifndef LEAN_CLIENT +static OM_uint32 +create_constrained_deleg_creds(OM_uint32 *minor_status, + krb5_gss_cred_id_t verifier_cred_handle, + krb5_ticket *ticket, + krb5_gss_cred_id_t *out_cred, + krb5_context context) +{ + OM_uint32 major_status; + krb5_creds krb_creds; + krb5_data *data; + krb5_error_code code; + + assert(out_cred != NULL); + assert(verifier_cred_handle->usage == GSS_C_BOTH); + + memset(&krb_creds, 0, sizeof(krb_creds)); + krb_creds.client = ticket->enc_part2->client; + krb_creds.server = ticket->server; + krb_creds.keyblock = *(ticket->enc_part2->session); + krb_creds.ticket_flags = ticket->enc_part2->flags; + krb_creds.times = ticket->enc_part2->times; + krb_creds.magic = KV5M_CREDS; + krb_creds.authdata = NULL; + + code = encode_krb5_ticket(ticket, &data); + if (code) { + *minor_status = code; + return GSS_S_FAILURE; + } + + krb_creds.ticket = *data; + + major_status = kg_compose_deleg_cred(minor_status, + verifier_cred_handle, + &krb_creds, + GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, + out_cred, + NULL, + NULL, + context); + + krb5_free_data(context, data); + + return major_status; +} + /* Decode, decrypt and store the forwarded creds in the local ccache. */ static krb5_error_code rd_and_store_for_creds(context, auth_context, inbuf, out_cred) @@ -866,6 +913,23 @@ kg_accept_krb5(minor_status, context_handle, ctx->krb_times = ticket->enc_part2->times; /* struct copy */ ctx->krb_flags = ticket->enc_part2->flags; + if (delegated_cred_handle != NULL && + deleg_cred == NULL && /* no unconstrained delegation */ + cred->usage == GSS_C_BOTH && + (ticket->enc_part2->flags & TKT_FLG_FORWARDABLE)) { + /* + * Now, we always fabricate a delegated credentials handle + * containing the service ticket to ourselves, which can be + * used for S4U2Proxy. + */ + major_status = create_constrained_deleg_creds(minor_status, cred, + ticket, &deleg_cred, + context); + if (GSS_ERROR(major_status)) + goto fail; + ctx->gss_flags |= GSS_C_DELEG_FLAG; + } + krb5_free_ticket(context, ticket); /* Done with ticket */ { @@ -1055,8 +1119,8 @@ kg_accept_krb5(minor_status, context_handle, if (src_name) *src_name = (gss_name_t) name; - if (delegated_cred_handle && deleg_cred) { - if (!kg_save_cred_id((gss_cred_id_t) deleg_cred)) { + if (delegated_cred_handle) { + if (!kg_save_cred_id((gss_cred_id_t) deleg_cred)) { major_status = GSS_S_FAILURE; code = G_VALIDATE_FAILED; goto fail; diff --git a/src/lib/gssapi/krb5/acquire_cred.c b/src/lib/gssapi/krb5/acquire_cred.c index 48471b4f4..4427ed763 100644 --- a/src/lib/gssapi/krb5/acquire_cred.c +++ b/src/lib/gssapi/krb5/acquire_cred.c @@ -532,8 +532,8 @@ krb5_gss_acquire_cred(minor_status, desired_name, time_req, cred->usage = cred_usage; cred->princ = NULL; - cred->prerfc_mech = req_old; - cred->rfc_mech = req_new; + cred->prerfc_mech = (req_old != 0); + cred->rfc_mech = (req_new != 0); #ifndef LEAN_CLIENT cred->keytab = NULL; @@ -759,3 +759,4 @@ gss_krb5int_set_cred_rcache(OM_uint32 *minor_status, *minor_status = 0; return GSS_S_COMPLETE; } + diff --git a/src/lib/gssapi/krb5/gssapiP_krb5.h b/src/lib/gssapi/krb5/gssapiP_krb5.h index a1073f344..48da87807 100644 --- a/src/lib/gssapi/krb5/gssapiP_krb5.h +++ b/src/lib/gssapi/krb5/gssapiP_krb5.h @@ -162,8 +162,9 @@ typedef struct _krb5_gss_cred_id_rec { /* name/type of credential */ gss_cred_usage_t usage; krb5_principal princ; /* this is not interned as a gss_name_t */ - int prerfc_mech; - int rfc_mech; + unsigned int prerfc_mech : 1; + unsigned int rfc_mech : 1; + unsigned int proxy_cred : 1; /* keytab (accept) data */ krb5_keytab keytab; @@ -466,6 +467,29 @@ krb5_boolean kg_integ_only_iov(gss_iov_buffer_desc *iov, int iov_count); krb5_error_code kg_allocate_iov(gss_iov_buffer_t iov, size_t size); +krb5_error_code +krb5_to_gss_cred(krb5_context context, + krb5_creds *creds, + krb5_gss_cred_id_t *out_cred); + +OM_uint32 +kg_new_connection( + OM_uint32 *minor_status, + krb5_gss_cred_id_t cred, + gss_ctx_id_t *context_handle, + gss_name_t target_name, + gss_OID mech_type, + OM_uint32 req_flags, + OM_uint32 time_req, + gss_channel_bindings_t input_chan_bindings, + gss_buffer_t input_token, + gss_OID *actual_mech_type, + gss_buffer_t output_token, + OM_uint32 *ret_flags, + OM_uint32 *time_rec, + krb5_context context, + int default_mech); + /** declarations of internal name mechanism functions **/ OM_uint32 krb5_gss_acquire_cred @@ -766,6 +790,17 @@ OM_uint32 krb5_gss_validate_cred gss_cred_id_t /* cred */ ); +OM_uint32 krb5_gss_acquire_cred_impersonate_name( + OM_uint32 *, /* minor_status */ + const gss_cred_id_t, /* impersonator_cred_handle */ + const gss_name_t, /* desired_name */ + OM_uint32, /* time_req */ + const gss_OID_set, /* desired_mechs */ + gss_cred_usage_t, /* cred_usage */ + gss_cred_id_t *, /* output_cred_handle */ + gss_OID_set *, /* actual_mechs */ + OM_uint32 *); /* time_rec */ + OM_uint32 krb5_gss_validate_cred_1(OM_uint32 * /* minor_status */, gss_cred_id_t /* cred_handle */, @@ -790,6 +825,19 @@ OM_uint32 gss_krb5int_unseal_token_v3(krb5_context *contextptr, int gss_krb5int_rotate_left (void *ptr, size_t bufsiz, size_t rc); +/* s4u_gss_glue.c */ +OM_uint32 +kg_compose_deleg_cred(OM_uint32 *minor_status, + krb5_gss_cred_id_t impersonator_cred, + krb5_creds *subject_creds, + OM_uint32 time_req, + const gss_OID_set desired_mechs, + krb5_gss_cred_id_t *output_cred, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec, + krb5_context context); + + /* * These take unglued krb5-mech-specific contexts. */ diff --git a/src/lib/gssapi/krb5/gssapi_krb5.c b/src/lib/gssapi/krb5/gssapi_krb5.c index a20e59dfb..519abb860 100644 --- a/src/lib/gssapi/krb5/gssapi_krb5.c +++ b/src/lib/gssapi/krb5/gssapi_krb5.c @@ -140,6 +140,7 @@ const gss_OID_desc krb5_gss_oid_array[] = { /* gss_nt_krb5_principal. Object identifier for a krb5_principal. Do not use. */ {10, "\052\206\110\206\367\022\001\002\002\002"}, + { 0, 0 } }; @@ -447,13 +448,11 @@ krb5_gss_inquire_cred_by_oid(OM_uint32 *minor_status, /* * gss_set_sec_context_option() methods */ -#if 0 static struct { gss_OID_desc oid; OM_uint32 (*func)(OM_uint32 *, gss_ctx_id_t *, const gss_OID, const gss_buffer_t); } krb5_gss_set_sec_context_option_ops[] = { }; -#endif static OM_uint32 krb5_gss_set_sec_context_option (OM_uint32 *minor_status, @@ -481,12 +480,8 @@ krb5_gss_set_sec_context_option (OM_uint32 *minor_status, return GSS_S_NO_CONTEXT; ctx = (krb5_gss_ctx_id_rec *) context_handle; - - if (!ctx->established) - return GSS_S_NO_CONTEXT; } -#if 0 for (i = 0; i < sizeof(krb5_gss_set_sec_context_option_ops)/ sizeof(krb5_gss_set_sec_context_option_ops[0]); i++) { if (g_OID_prefix_equal(desired_object, &krb5_gss_set_sec_context_option_ops[i].oid)) { @@ -496,7 +491,6 @@ krb5_gss_set_sec_context_option (OM_uint32 *minor_status, value); } } -#endif *minor_status = EINVAL; @@ -521,7 +515,7 @@ static struct { { {GSS_KRB5_SET_CRED_RCACHE_OID_LENGTH, GSS_KRB5_SET_CRED_RCACHE_OID}, gss_krb5int_set_cred_rcache - } + }, }; static OM_uint32 @@ -587,7 +581,7 @@ static struct { { {GSS_KRB5_USE_KDC_CONTEXT_OID_LENGTH, GSS_KRB5_USE_KDC_CONTEXT_OID}, krb5int_gss_use_kdc_context - } + }, }; static OM_uint32 @@ -683,6 +677,8 @@ static struct gss_config krb5_mechanism = { krb5_gss_unwrap_iov, krb5_gss_wrap_iov_length, NULL, /* complete_auth_token */ + krb5_gss_acquire_cred_impersonate_name, + NULL, /* krb5_gss_add_cred_impersonate_name */ }; diff --git a/src/lib/gssapi/krb5/import_name.c b/src/lib/gssapi/krb5/import_name.c index 440d36222..6879c766f 100644 --- a/src/lib/gssapi/krb5/import_name.c +++ b/src/lib/gssapi/krb5/import_name.c @@ -56,8 +56,7 @@ krb5_gss_import_name(minor_status, input_name_buffer, krb5_context context; krb5_principal princ; krb5_error_code code; - unsigned char *cp, *end; - char *stringrep, *tmp, *tmp2; + char *stringrep, *tmp, *tmp2, *cp; OM_uint32 length; #ifndef NO_PASSWORD struct passwd *pw; @@ -156,12 +155,7 @@ krb5_gss_import_name(minor_status, input_name_buffer, goto do_getpwuid; #endif } else if (g_OID_equal(input_name_type, gss_nt_exported_name)) { -#define BOUNDS_CHECK(cp, end, n) do { if ((end) - (cp) < (n)) \ - goto fail_name; } while (0) - cp = (unsigned char *)tmp; - end = cp + input_name_buffer->length; - - BOUNDS_CHECK(cp, end, 4); + cp = tmp; if (*cp++ != 0x04) goto fail_name; if (*cp++ != 0x01) @@ -169,28 +163,20 @@ krb5_gss_import_name(minor_status, input_name_buffer, if (*cp++ != 0x00) goto fail_name; length = *cp++; - if (length != (ssize_t)gss_mech_krb5->length+2) + if (length != gss_mech_krb5->length+2) goto fail_name; - - BOUNDS_CHECK(cp, end, 2); if (*cp++ != 0x06) goto fail_name; length = *cp++; if (length != gss_mech_krb5->length) goto fail_name; - - BOUNDS_CHECK(cp, end, length); if (memcmp(cp, gss_mech_krb5->elements, length) != 0) goto fail_name; cp += length; - - BOUNDS_CHECK(cp, end, 4); length = *cp++; length = (length << 8) | *cp++; length = (length << 8) | *cp++; length = (length << 8) | *cp++; - - BOUNDS_CHECK(cp, end, length); tmp2 = malloc(length+1); if (tmp2 == NULL) { xfree(tmp); @@ -198,7 +184,7 @@ krb5_gss_import_name(minor_status, input_name_buffer, krb5_free_context(context); return GSS_S_FAILURE; } - strncpy(tmp2, (char *)cp, length); + strncpy(tmp2, cp, length); tmp2[length] = 0; stringrep = tmp2; diff --git a/src/lib/gssapi/krb5/init_sec_context.c b/src/lib/gssapi/krb5/init_sec_context.c index 5559fadbc..0bb4fde02 100644 --- a/src/lib/gssapi/krb5/init_sec_context.c +++ b/src/lib/gssapi/krb5/init_sec_context.c @@ -128,25 +128,69 @@ static krb5_error_code get_credentials(context, cred, server, now, krb5_creds **out_creds; { krb5_error_code code; - krb5_creds in_creds; + krb5_creds in_creds, evidence_creds; + krb5_flags flags = 0; + krb5_principal cc_princ = NULL; k5_mutex_assert_locked(&cred->lock); memset(&in_creds, 0, sizeof(krb5_creds)); + memset(&evidence_creds, 0, sizeof(krb5_creds)); in_creds.client = in_creds.server = NULL; - if ((code = krb5_copy_principal(context, cred->princ, &in_creds.client))) + if ((code = krb5_cc_get_principal(context, cred->ccache, &cc_princ))) goto cleanup; - if ((code = krb5_copy_principal(context, server, &in_creds.server))) - goto cleanup; - in_creds.times.endtime = endtime; - in_creds.keyblock.enctype = 0; + /* + * Do constrained delegation if we have proxy credentials and + * we're not trying to get a ticket to ourselves (in which case + * we can just use the S4U2Self or evidence ticket directly). + */ + if (cred->proxy_cred && + !krb5_principal_compare(context, cc_princ, server)) { + krb5_creds mcreds; + + flags |= KRB5_GC_CANONICALIZE | + KRB5_GC_NO_STORE | + KRB5_GC_CONSTRAINED_DELEGATION; + + memset(&mcreds, 0, sizeof(mcreds)); - code = krb5_get_credentials(context, 0, cred->ccache, + mcreds.magic = KV5M_CREDS; + mcreds.times.endtime = cred->tgt_expire; + mcreds.server = cc_princ; + mcreds.client = cred->princ; + + code = krb5_cc_retrieve_cred(context, cred->ccache, + KRB5_TC_MATCH_TIMES, &mcreds, + &evidence_creds); + if (code) + goto cleanup; + + assert(evidence_creds.ticket_flags & TKT_FLG_FORWARDABLE); + + in_creds.client = cc_princ; + in_creds.second_ticket = evidence_creds.ticket; + } else { + in_creds.client = cred->princ; + } + + in_creds.server = server; + in_creds.times.endtime = endtime; + + code = krb5_get_credentials(context, flags, cred->ccache, &in_creds, out_creds); if (code) goto cleanup; + if (flags & KRB5_GC_CONSTRAINED_DELEGATION) { + if (!krb5_principal_compare(context, cred->princ, + (*out_creds)->client)) { + /* server did not support constrained delegation */ + code = KRB5_KDCREP_MODIFIED; + goto cleanup; + } + } + /* * Enforce a stricter limit (without timeskew forgiveness at the * boundaries) because accept_sec_context code is also similarly @@ -159,10 +203,10 @@ static krb5_error_code get_credentials(context, cred, server, now, } cleanup: - if (in_creds.client) - krb5_free_principal(context, in_creds.client); - if (in_creds.server) - krb5_free_principal(context, in_creds.server); + if (cc_princ) + krb5_free_principal(context, cc_princ); + krb5_free_cred_contents(context, &evidence_creds); + return code; } struct gss_checksum_data { @@ -390,8 +434,8 @@ cleanup: * * Do the grunt work of setting up a new context. */ -static OM_uint32 -new_connection( +OM_uint32 +kg_new_connection( OM_uint32 *minor_status, krb5_gss_cred_id_t cred, gss_ctx_id_t *context_handle, @@ -931,12 +975,12 @@ krb5_gss_init_sec_context(minor_status, claimant_cred_handle, /*SUPPRESS 29*/ if (*context_handle == GSS_C_NO_CONTEXT) { - major_status = new_connection(minor_status, cred, context_handle, - target_name, mech_type, req_flags, - time_req, input_chan_bindings, - input_token, actual_mech_type, - output_token, ret_flags, time_rec, - context, default_mech); + major_status = kg_new_connection(minor_status, cred, context_handle, + target_name, mech_type, req_flags, + time_req, input_chan_bindings, + input_token, actual_mech_type, + output_token, ret_flags, time_rec, + context, default_mech); k5_mutex_unlock(&cred->lock); if (*context_handle == GSS_C_NO_CONTEXT) { save_error_info (*minor_status, context); diff --git a/src/lib/gssapi/krb5/krb5_gss_glue.c b/src/lib/gssapi/krb5/krb5_gss_glue.c index f9bf03016..034550122 100644 --- a/src/lib/gssapi/krb5/krb5_gss_glue.c +++ b/src/lib/gssapi/krb5/krb5_gss_glue.c @@ -416,3 +416,4 @@ gsskrb5_extract_authtime_from_sec_context(OM_uint32 *minor_status, return GSS_S_COMPLETE; } + diff --git a/src/lib/gssapi/krb5/s4u_gss_glue.c b/src/lib/gssapi/krb5/s4u_gss_glue.c new file mode 100644 index 000000000..8e2d690b1 --- /dev/null +++ b/src/lib/gssapi/krb5/s4u_gss_glue.c @@ -0,0 +1,346 @@ +/* -*- mode: c; indent-tabs-mode: nil -*- */ +/* + * Copyright 2009 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 "gssapiP_krb5.h" +#ifdef HAVE_MEMORY_H +#include <memory.h> +#endif +#include <assert.h> + +static OM_uint32 +kg_set_desired_mechs(OM_uint32 *minor_status, + const gss_OID_set desired_mechs, + krb5_gss_cred_id_t cred) +{ + unsigned int i; + + if (desired_mechs == GSS_C_NULL_OID_SET) { + cred->prerfc_mech = 1; + cred->rfc_mech = 1; + } else { + cred->prerfc_mech = 0; + cred->rfc_mech = 0; + + for (i = 0; i < desired_mechs->count; i++) { + if (g_OID_equal(gss_mech_krb5_old, &desired_mechs->elements[i])) + cred->prerfc_mech = 1; + else if (g_OID_equal(gss_mech_krb5, &desired_mechs->elements[i])) + cred->rfc_mech = 1; + } + + if (!cred->prerfc_mech && !cred->rfc_mech) { + *minor_status = 0; + return GSS_S_BAD_MECH; + } + } + + return GSS_S_COMPLETE; +} + +static OM_uint32 +kg_return_mechs(OM_uint32 *minor_status, + krb5_gss_cred_id_t cred, + gss_OID_set *actual_mechs) +{ + OM_uint32 major_status, minor; + gss_OID_set mechs; + + if (actual_mechs == NULL) + return GSS_S_COMPLETE; + + major_status = generic_gss_create_empty_oid_set(minor_status, &mechs); + if (GSS_ERROR(major_status)) + return major_status; + + if (cred->prerfc_mech) { + major_status = generic_gss_add_oid_set_member(minor_status, + gss_mech_krb5_old, + &mechs); + if (GSS_ERROR(major_status)) { + generic_gss_release_oid_set(&minor, &mechs); + return major_status; + } + } + if (cred->rfc_mech) { + major_status = generic_gss_add_oid_set_member(minor_status, + gss_mech_krb5, + &mechs); + if (GSS_ERROR(major_status)) { + generic_gss_release_oid_set(&minor, &mechs); + return major_status; + } + } + + *actual_mechs = mechs; + + return GSS_S_COMPLETE; +} + +static int +kg_is_initiator_cred(krb5_gss_cred_id_t cred) +{ + return (cred->usage == GSS_C_INITIATE || cred->usage == GSS_C_BOTH) && + (cred->ccache != NULL); +} + +static OM_uint32 +kg_impersonate_name(OM_uint32 *minor_status, + const krb5_gss_cred_id_t impersonator_cred, + const krb5_principal user, + OM_uint32 time_req, + const gss_OID_set desired_mechs, + krb5_gss_cred_id_t *output_cred, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec, + krb5_context context) +{ + OM_uint32 major_status; + krb5_error_code code; + krb5_creds in_creds, *out_creds = NULL; + + memset(&in_creds, 0, sizeof(in_creds)); + memset(&out_creds, 0, sizeof(out_creds)); + + in_creds.client = user; + in_creds.server = impersonator_cred->princ; + + if (impersonator_cred->req_enctypes != NULL) + in_creds.keyblock.enctype = impersonator_cred->req_enctypes[0]; + + code = krb5_get_credentials_for_user(context, + KRB5_GC_CANONICALIZE | KRB5_GC_NO_STORE, + impersonator_cred->ccache, + &in_creds, + NULL, &out_creds); + if (code != 0) { + *minor_status = code; + return GSS_S_FAILURE; + } + + major_status = kg_compose_deleg_cred(minor_status, + impersonator_cred, + out_creds, + time_req, + desired_mechs, + output_cred, + actual_mechs, + time_rec, + context); + + krb5_free_creds(context, out_creds); + + return major_status; +} + +OM_uint32 +krb5_gss_acquire_cred_impersonate_name(OM_uint32 *minor_status, + const gss_cred_id_t impersonator_cred_handle, + const gss_name_t desired_name, + OM_uint32 time_req, + const gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ + OM_uint32 major_status; + krb5_error_code code; + krb5_gss_cred_id_t cred; + krb5_context context; + + if (impersonator_cred_handle == GSS_C_NO_CREDENTIAL) + return GSS_S_CALL_INACCESSIBLE_READ; + + if (desired_name == GSS_C_NO_NAME) + return GSS_S_CALL_INACCESSIBLE_READ; + + if (output_cred_handle == NULL) + return GSS_S_CALL_INACCESSIBLE_WRITE; + + if (cred_usage != GSS_C_INITIATE) { + *minor_status = (OM_uint32)G_BAD_USAGE; + return GSS_S_FAILURE; + } + + *output_cred_handle = GSS_C_NO_CREDENTIAL; + if (actual_mechs != NULL) + *actual_mechs = GSS_C_NO_OID_SET; + if (time_rec != NULL) + *time_rec = 0; + + code = krb5_gss_init_context(&context); + if (code != 0) { + *minor_status = code; + return GSS_S_FAILURE; + } + + major_status = krb5_gss_validate_cred_1(minor_status, + impersonator_cred_handle, + context); + if (GSS_ERROR(major_status)) { + krb5_free_context(context); + return major_status; + } + + major_status = kg_impersonate_name(minor_status, + (krb5_gss_cred_id_t)impersonator_cred_handle, + (krb5_principal)desired_name, + time_req, + desired_mechs, + &cred, + actual_mechs, + time_rec, + context); + + *output_cred_handle = (gss_cred_id_t)cred; + + k5_mutex_unlock(&((krb5_gss_cred_id_t)impersonator_cred_handle)->lock); + krb5_free_context(context); + + return major_status; + +} + +OM_uint32 +kg_compose_deleg_cred(OM_uint32 *minor_status, + krb5_gss_cred_id_t impersonator_cred, + krb5_creds *subject_creds, + OM_uint32 time_req, + const gss_OID_set desired_mechs, + krb5_gss_cred_id_t *output_cred, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec, + krb5_context context) +{ + OM_uint32 major_status; + krb5_error_code code; + krb5_gss_cred_id_t cred = NULL; + + k5_mutex_assert_locked(&impersonator_cred->lock); + + if (!kg_is_initiator_cred(impersonator_cred) || + impersonator_cred->princ == NULL || + impersonator_cred->proxy_cred) { + code = G_BAD_USAGE; + goto cleanup; + } + + assert(subject_creds != NULL); + assert(subject_creds->client != NULL); + + cred = xmalloc(sizeof(*cred)); + if (cred == NULL) { + code = ENOMEM; + goto cleanup; + } + memset(cred, 0, sizeof(*cred)); + + code = k5_mutex_init(&cred->lock); + if (code != 0) + goto cleanup; + + /* + * Only return a "proxy" credential for use with constrained + * delegation if the subject credentials are forwardable. + * Submitting non-forwardable credentials to the KDC for use + * with constrained delegation will only return an error. + */ + cred->usage = GSS_C_INITIATE; + cred->proxy_cred = !!(subject_creds->ticket_flags & TKT_FLG_FORWARDABLE); + + major_status = kg_set_desired_mechs(minor_status, desired_mechs, cred); + if (GSS_ERROR(major_status)) + goto cleanup; + + cred->tgt_expire = impersonator_cred->tgt_expire; + + code = krb5_copy_principal(context, subject_creds->client, &cred->princ); + if (code != 0) + goto cleanup; + + code = krb5_cc_new_unique(context, "MEMORY", NULL, &cred->ccache); + if (code != 0) + goto cleanup; + + code = krb5_cc_initialize(context, cred->ccache, + cred->proxy_cred ? impersonator_cred->princ : + (krb5_principal)subject_creds->client); + if (code != 0) + goto cleanup; + + if (cred->proxy_cred) { + /* Impersonator's TGT will be necessary for S4U2Proxy */ + code = krb5_cc_copy_creds(context, impersonator_cred->ccache, + cred->ccache); + if (code != 0) + goto cleanup; + } + + code = krb5_cc_store_cred(context, cred->ccache, subject_creds); + if (code != 0) + goto cleanup; + + if (time_rec != NULL) { + krb5_timestamp now; + + code = krb5_timeofday(context, &now); + if (code != 0) + goto cleanup; + + *time_rec = cred->tgt_expire - now; + } + + major_status = kg_return_mechs(minor_status, cred, actual_mechs); + if (GSS_ERROR(major_status)) + goto cleanup; + + if (!kg_save_cred_id((gss_cred_id_t)cred)) { + code = G_VALIDATE_FAILED; + goto cleanup; + } + + major_status = GSS_S_COMPLETE; + *minor_status = 0; + *output_cred = cred; + +cleanup: + if (code != 0) { + *minor_status = code; + major_status = GSS_S_FAILURE; + } + + if (GSS_ERROR(major_status) && cred != NULL) { + k5_mutex_destroy(&cred->lock); + if (cred->ccache != NULL) + krb5_cc_destroy(context, cred->ccache); + if (cred->princ != NULL) + krb5_free_principal(context, cred->princ); + xfree(cred); + } + + return major_status; +} + diff --git a/src/lib/gssapi/krb5/val_cred.c b/src/lib/gssapi/krb5/val_cred.c index dd82d5341..43b1f695d 100644 --- a/src/lib/gssapi/krb5/val_cred.c +++ b/src/lib/gssapi/krb5/val_cred.c @@ -58,7 +58,8 @@ krb5_gss_validate_cred_1(OM_uint32 *minor_status, gss_cred_id_t cred_handle, *minor_status = code; return(GSS_S_DEFECTIVE_CREDENTIAL); } - if (!krb5_principal_compare(context, princ, cred->princ)) { + if (!cred->proxy_cred && + !krb5_principal_compare(context, princ, cred->princ)) { k5_mutex_unlock(&cred->lock); *minor_status = KG_CCACHE_NOMATCH; return(GSS_S_DEFECTIVE_CREDENTIAL); diff --git a/src/lib/gssapi/libgssapi_krb5.exports b/src/lib/gssapi/libgssapi_krb5.exports index 69f390e45..d641fc65b 100644 --- a/src/lib/gssapi/libgssapi_krb5.exports +++ b/src/lib/gssapi/libgssapi_krb5.exports @@ -9,8 +9,10 @@ GSS_C_NT_USER_NAME GSS_KRB5_NT_PRINCIPAL_NAME gss_accept_sec_context gss_acquire_cred +gss_acquire_cred_impersonate_name gss_add_buffer_set_member gss_add_cred +gss_add_cred_impersonate_name gss_add_oid_set_member gss_canonicalize_name gss_compare_name diff --git a/src/lib/gssapi/mechglue/Makefile.in b/src/lib/gssapi/mechglue/Makefile.in index 927b3755c..18e89f19d 100644 --- a/src/lib/gssapi/mechglue/Makefile.in +++ b/src/lib/gssapi/mechglue/Makefile.in @@ -14,6 +14,7 @@ DEFS=-D_GSS_STATIC_LINK=1 SRCS = \ $(srcdir)/g_accept_sec_context.c \ $(srcdir)/g_acquire_cred.c \ + $(srcdir)/g_acquire_cred_imp_name.c \ $(srcdir)/g_buffer_set.c \ $(srcdir)/g_canon_name.c \ $(srcdir)/g_compare_name.c \ @@ -58,6 +59,7 @@ SRCS = \ OBJS = \ $(OUTPRE)g_accept_sec_context.$(OBJEXT) \ $(OUTPRE)g_acquire_cred.$(OBJEXT) \ + $(OUTPRE)g_acquire_cred_imp_name.$(OBJEXT) \ $(OUTPRE)g_buffer_set.$(OBJEXT) \ $(OUTPRE)g_canon_name.$(OBJEXT) \ $(OUTPRE)g_compare_name.$(OBJEXT) \ @@ -102,6 +104,7 @@ OBJS = \ STLIBOBJS = \ g_accept_sec_context.o \ g_acquire_cred.o \ + g_acquire_cred_imp_name.o \ g_buffer_set.o \ g_canon_name.o \ g_compare_name.o \ diff --git a/src/lib/gssapi/mechglue/g_accept_sec_context.c b/src/lib/gssapi/mechglue/g_accept_sec_context.c index fa703d34d..dc4391593 100644 --- a/src/lib/gssapi/mechglue/g_accept_sec_context.c +++ b/src/lib/gssapi/mechglue/g_accept_sec_context.c @@ -121,6 +121,7 @@ gss_cred_id_t * d_cred; gss_name_t tmp_src_name = GSS_C_NO_NAME; gss_OID_desc token_mech_type_desc; gss_OID token_mech_type = &token_mech_type_desc; + gss_OID actual_mech = GSS_C_NO_OID; gss_mechanism mech; status = val_acc_sec_ctx_args(minor_status, @@ -198,8 +199,8 @@ gss_cred_id_t * d_cred; input_cred_handle, input_token_buffer, input_chan_bindings, - &internal_name, - mech_type, + src_name ? &internal_name : NULL, + &actual_mech, output_token, &temp_ret_flags, time_rec, @@ -222,110 +223,120 @@ gss_cred_id_t * d_cred; * then call gss_import_name() to create * the union name struct cast to src_name */ - if (internal_name != NULL) { - temp_status = gssint_convert_name_to_union_name( - &temp_minor_status, mech, - internal_name, &tmp_src_name); - if (temp_status != GSS_S_COMPLETE) { - *minor_status = temp_minor_status; - map_error(minor_status, mech); - if (output_token->length) - (void) gss_release_buffer(&temp_minor_status, - output_token); - if (internal_name != GSS_C_NO_NAME) - mech->gss_release_name( - &temp_minor_status, - &internal_name); - return (temp_status); - } - if (src_name != NULL) { + if (src_name != NULL) { + if (internal_name != GSS_C_NO_NAME) { + /* consumes internal_name regardless of success */ + temp_status = gssint_convert_name_to_union_name( + &temp_minor_status, mech, + internal_name, &tmp_src_name); + if (temp_status != GSS_S_COMPLETE) { + *minor_status = temp_minor_status; + map_error(minor_status, mech); + if (output_token->length) + (void) gss_release_buffer(&temp_minor_status, + output_token); + return (temp_status); + } *src_name = tmp_src_name; - } - } else if (src_name != NULL) { - *src_name = GSS_C_NO_NAME; + } else + *src_name = GSS_C_NO_NAME; } +#define g_OID_prefix_equal(o1, o2) \ + (((o1)->length >= (o2)->length) && \ + (memcmp((o1)->elements, (o2)->elements, (o2)->length) == 0)) + /* Ensure we're returning correct creds format */ if ((temp_ret_flags & GSS_C_DELEG_FLAG) && tmp_d_cred != GSS_C_NO_CREDENTIAL) { - gss_union_cred_t d_u_cred = NULL; - - d_u_cred = malloc(sizeof (gss_union_cred_desc)); - if (d_u_cred == NULL) { - status = GSS_S_FAILURE; - goto error_out; - } - (void) memset(d_u_cred, 0, - sizeof (gss_union_cred_desc)); - - d_u_cred->count = 1; + if (actual_mech != GSS_C_NO_OID && + !g_OID_prefix_equal(actual_mech, token_mech_type)) { + *d_cred = tmp_d_cred; /* unwrapped pseudo-mech */ + } else { + gss_union_cred_t d_u_cred = NULL; - status = generic_gss_copy_oid(&temp_minor_status, - token_mech_type, - &d_u_cred->mechs_array); + d_u_cred = malloc(sizeof (gss_union_cred_desc)); + if (d_u_cred == NULL) { + status = GSS_S_FAILURE; + goto error_out; + } + (void) memset(d_u_cred, 0, sizeof (gss_union_cred_desc)); - if (status != GSS_S_COMPLETE) { - free(d_u_cred); - goto error_out; - } + d_u_cred->count = 1; - d_u_cred->cred_array = malloc(sizeof (gss_cred_id_t)); - if (d_u_cred->cred_array != NULL) { - d_u_cred->cred_array[0] = tmp_d_cred; - } else { - free(d_u_cred); - status = GSS_S_FAILURE; - goto error_out; - } + status = generic_gss_copy_oid(&temp_minor_status, + token_mech_type, + &d_u_cred->mechs_array); - internal_name = GSS_C_NO_NAME; + if (status != GSS_S_COMPLETE) { + free(d_u_cred); + goto error_out; + } - d_u_cred->auxinfo.creation_time = time(0); - d_u_cred->auxinfo.time_rec = 0; - d_u_cred->loopback = d_u_cred; + d_u_cred->cred_array = malloc(sizeof(gss_cred_id_t)); + if (d_u_cred->cred_array != NULL) { + d_u_cred->cred_array[0] = tmp_d_cred; + } else { + free(d_u_cred); + status = GSS_S_FAILURE; + goto error_out; + } - if (mech->gss_inquire_cred) { - status = mech->gss_inquire_cred(minor_status, - tmp_d_cred, - &internal_name, - &d_u_cred->auxinfo.time_rec, - &d_u_cred->auxinfo.cred_usage, - NULL); - if (status != GSS_S_COMPLETE) - map_error(minor_status, mech); - } + d_u_cred->auxinfo.creation_time = time(0); + d_u_cred->auxinfo.time_rec = 0; + d_u_cred->loopback = d_u_cred; + + internal_name = GSS_C_NO_NAME; + + if (mech->gss_inquire_cred) { + status = mech->gss_inquire_cred(minor_status, + tmp_d_cred, + &internal_name, + &d_u_cred->auxinfo.time_rec, + &d_u_cred->auxinfo.cred_usage, + NULL); + if (status != GSS_S_COMPLETE) + map_error(minor_status, mech); + } - if (internal_name != NULL) { - temp_status = gssint_convert_name_to_union_name( - &temp_minor_status, mech, - internal_name, &tmp_src_name); - if (temp_status != GSS_S_COMPLETE) { - *minor_status = temp_minor_status; - map_error(minor_status, mech); - if (output_token->length) - (void) gss_release_buffer( + if (internal_name != GSS_C_NO_NAME) { + /* consumes internal_name regardless of success */ + temp_status = gssint_convert_name_to_union_name( + &temp_minor_status, mech, + internal_name, &tmp_src_name); + if (temp_status != GSS_S_COMPLETE) { + *minor_status = temp_minor_status; + map_error(minor_status, mech); + if (output_token->length) + (void) gss_release_buffer( + &temp_minor_status, + output_token); + (void) gss_release_oid(&temp_minor_status, + &actual_mech); + free(d_u_cred->cred_array); + free(d_u_cred); + return (temp_status); + } + + if (tmp_src_name != GSS_C_NO_NAME) { + status = gss_display_name( &temp_minor_status, - output_token); - free(d_u_cred->cred_array); - free(d_u_cred); - return (temp_status); + tmp_src_name, + &d_u_cred->auxinfo.name, + &d_u_cred->auxinfo.name_type); + (void) gss_release_name(&temp_minor_status, + &tmp_src_name); + } } - } - if (tmp_src_name != NULL) { - status = gss_display_name( - &temp_minor_status, - tmp_src_name, - &d_u_cred->auxinfo.name, - &d_u_cred->auxinfo.name_type); + *d_cred = (gss_cred_id_t)d_u_cred; } - - *d_cred = (gss_cred_id_t)d_u_cred; } - if (src_name == NULL && tmp_src_name != NULL) - (void) gss_release_name(&temp_minor_status, - &tmp_src_name); + if (mech_type != NULL) + *mech_type = actual_mech; + else + (void) gss_release_oid(&temp_minor_status, &actual_mech); if (ret_flags != NULL) *ret_flags = temp_ret_flags; return (status); diff --git a/src/lib/gssapi/mechglue/g_acquire_cred.c b/src/lib/gssapi/mechglue/g_acquire_cred.c index fada9e887..6dfc65f7b 100644 --- a/src/lib/gssapi/mechglue/g_acquire_cred.c +++ b/src/lib/gssapi/mechglue/g_acquire_cred.c @@ -2,7 +2,7 @@ /* * Copyright 1996 by Sun Microsystems, Inc. - * + * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appears in all copies and @@ -12,7 +12,7 @@ * without specific, written prior permission. Sun Microsystems makes no * representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied warranty. - * + * * SUN MICROSYSTEMS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO * EVENT SHALL SUN MICROSYSTEMS BE LIABLE FOR ANY SPECIAL, INDIRECT OR @@ -35,42 +35,6 @@ #include <errno.h> #include <time.h> -static gss_OID_set -create_actual_mechs(mechs_array, count) - const gss_OID mechs_array; - int count; -{ - gss_OID_set actual_mechs; - int i; - OM_uint32 minor; - - actual_mechs = (gss_OID_set) malloc(sizeof(gss_OID_set_desc)); - if (!actual_mechs) - return NULL; - - actual_mechs->elements = (gss_OID) - malloc(sizeof (gss_OID_desc) * count); - if (!actual_mechs->elements) { - free(actual_mechs); - return NULL; - } - - actual_mechs->count = 0; - - for (i = 0; i < count; i++) { - actual_mechs->elements[i].elements = (void *) - malloc(mechs_array[i].length); - if (actual_mechs->elements[i].elements == NULL) { - (void) gss_release_oid_set(&minor, &actual_mechs); - return (NULL); - } - g_OID_copy(&actual_mechs->elements[i], &mechs_array[i]); - actual_mechs->count++; - } - - return actual_mechs; -} - static OM_uint32 val_acq_cred_args( OM_uint32 *minor_status, @@ -172,7 +136,7 @@ OM_uint32 * time_rec; mech = gssint_get_mechanism(NULL); if (mech == NULL) return (GSS_S_BAD_MECH); - + mechs = &default_OID_set; default_OID_set.count = 1; default_OID_set.elements = &default_OID; @@ -234,12 +198,16 @@ OM_uint32 * time_rec; * setup the actual mechs output parameter */ if (actual_mechs != NULL) { - if ((*actual_mechs = create_actual_mechs(creds->mechs_array, - creds->count)) == NULL) { + gss_OID_set_desc oids; + + oids.count = creds->count; + oids.elements = creds->mechs_array; + + major = generic_gss_copy_oid_set(minor_status, &oids, actual_mechs); + if (GSS_ERROR(major)) { (void) gss_release_cred(minor_status, (gss_cred_id_t *)&creds); - *minor_status = 0; - return (GSS_S_FAILURE); + return (major); } } @@ -312,7 +280,7 @@ OM_uint32 KRB5_CALLCONV gss_add_cred(minor_status, input_cred_handle, desired_name, desired_mech, cred_usage, initiator_time_req, acceptor_time_req, - output_cred_handle, actual_mechs, + output_cred_handle, actual_mechs, initiator_time_rec, acceptor_time_rec) OM_uint32 *minor_status; gss_cred_id_t input_cred_handle; @@ -434,7 +402,7 @@ gss_add_cred(minor_status, input_cred_handle, status = mech->gss_display_name(&temp_minor_status, internal_name, &union_cred->auxinfo.name, &union_cred->auxinfo.name_type); - + if (status != GSS_S_COMPLETE) goto errout; } @@ -475,10 +443,14 @@ gss_add_cred(minor_status, input_cred_handle, g_OID_copy(&new_mechs_array[union_cred->count], &mech->mech_type); - if (actual_mechs) { - *actual_mechs = create_actual_mechs(new_mechs_array, - union_cred->count + 1); - if (*actual_mechs == NULL) { + if (actual_mechs != NULL) { + gss_OID_set_desc oids; + + oids.count = union_cred->count + 1; + oids.elements = new_mechs_array; + + status = generic_gss_copy_oid_set(minor_status, &oids, actual_mechs); + if (GSS_ERROR(status)) { free(new_mechs_array[union_cred->count].elements); goto errout; } diff --git a/src/lib/gssapi/mechglue/g_acquire_cred_imp_name.c b/src/lib/gssapi/mechglue/g_acquire_cred_imp_name.c new file mode 100644 index 000000000..9ba6a1faa --- /dev/null +++ b/src/lib/gssapi/mechglue/g_acquire_cred_imp_name.c @@ -0,0 +1,549 @@ +/* #pragma ident "@(#)g_acquire_cred.c 1.22 04/02/23 SMI" */ + +/* + * Copyright 2009 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. + * + */ +/* + * Copyright 1996 by Sun Microsystems, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without fee, + * provided that the above copyright notice appears in all copies and + * that both that copyright notice and this permission notice appear in + * supporting documentation, and that the name of Sun Microsystems not be used + * in advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. Sun Microsystems makes no + * representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied warranty. + * + * SUN MICROSYSTEMS DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO + * EVENT SHALL SUN MICROSYSTEMS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF + * USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * glue routine for gss_acquire_cred_impersonate_name + */ + +#include "mglueP.h" +#include <stdio.h> +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#include <string.h> +#include <errno.h> +#include <time.h> + +static OM_uint32 +val_acq_cred_impersonate_name_args( + OM_uint32 *minor_status, + const gss_cred_id_t impersonator_cred_handle, + const gss_name_t desired_name, + OM_uint32 time_req, + gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ + + /* Initialize outputs. */ + + if (minor_status != NULL) + *minor_status = 0; + + if (output_cred_handle != NULL) + *output_cred_handle = GSS_C_NO_CREDENTIAL; + + if (actual_mechs != NULL) + *actual_mechs = GSS_C_NULL_OID_SET; + + if (time_rec != NULL) + *time_rec = 0; + + /* Validate arguments. */ + + if (minor_status == NULL) + return (GSS_S_CALL_INACCESSIBLE_WRITE); + + if (impersonator_cred_handle == GSS_C_NO_CREDENTIAL) + return (GSS_S_CALL_INACCESSIBLE_READ | GSS_S_NO_CRED); + + if (desired_name == GSS_C_NO_NAME) + return (GSS_S_CALL_INACCESSIBLE_READ | GSS_S_BAD_NAME); + + if (output_cred_handle == NULL) + return (GSS_S_CALL_INACCESSIBLE_WRITE); + + if (cred_usage != GSS_C_ACCEPT + && cred_usage != GSS_C_INITIATE + && cred_usage != GSS_C_BOTH) { + if (minor_status) { + *minor_status = EINVAL; + map_errcode(minor_status); + } + return GSS_S_FAILURE; + } + + return (GSS_S_COMPLETE); +} + + +OM_uint32 KRB5_CALLCONV +gss_acquire_cred_impersonate_name(OM_uint32 *minor_status, + const gss_cred_id_t impersonator_cred_handle, + const gss_name_t desired_name, + OM_uint32 time_req, + const gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ + OM_uint32 major = GSS_S_FAILURE; + OM_uint32 initTimeOut, acceptTimeOut, outTime = GSS_C_INDEFINITE; + gss_OID_set_desc default_OID_set; + gss_OID_set mechs; + gss_OID_desc default_OID; + gss_mechanism mech; + unsigned int i; + gss_union_cred_t creds; + + major = val_acq_cred_impersonate_name_args(minor_status, + impersonator_cred_handle, + desired_name, + time_req, + desired_mechs, + cred_usage, + output_cred_handle, + actual_mechs, + time_rec); + if (major != GSS_S_COMPLETE) + return (major); + + /* Initial value needed below. */ + major = GSS_S_FAILURE; + + /* + * if desired_mechs equals GSS_C_NULL_OID_SET, then pick an + * appropriate default. We use the first mechanism in the + * mechansim list as the default. This set is created with + * statics thus needs not be freed + */ + if(desired_mechs == GSS_C_NULL_OID_SET) { + mech = gssint_get_mechanism(NULL); + if (mech == NULL) + return (GSS_S_BAD_MECH); + + mechs = &default_OID_set; + default_OID_set.count = 1; + default_OID_set.elements = &default_OID; + default_OID.length = mech->mech_type.length; + default_OID.elements = mech->mech_type.elements; + } else + mechs = desired_mechs; + + if (mechs->count == 0) + return (GSS_S_BAD_MECH); + + /* allocate the output credential structure */ + creds = (gss_union_cred_t)malloc(sizeof (gss_union_cred_desc)); + if (creds == NULL) + return (GSS_S_FAILURE); + + /* initialize to 0s */ + (void) memset(creds, 0, sizeof (gss_union_cred_desc)); + creds->loopback = creds; + + /* for each requested mech attempt to obtain a credential */ + for (i = 0; i < mechs->count; i++) { + major = gss_add_cred_impersonate_name(minor_status, + (gss_cred_id_t)creds, + impersonator_cred_handle, + desired_name, + &mechs->elements[i], + cred_usage, + time_req, + time_req, NULL, + NULL, + &initTimeOut, + &acceptTimeOut); + if (major == GSS_S_COMPLETE) { + /* update the credential's time */ + if (cred_usage == GSS_C_ACCEPT) { + if (outTime > acceptTimeOut) + outTime = acceptTimeOut; + } else if (cred_usage == GSS_C_INITIATE) { + if (outTime > initTimeOut) + outTime = initTimeOut; + } else { + /* + * time_rec is the lesser of the + * init/accept times + */ + if (initTimeOut > acceptTimeOut) + outTime = (outTime > acceptTimeOut) ? + acceptTimeOut : outTime; + else + outTime = (outTime > initTimeOut) ? + initTimeOut : outTime; + } + } + } /* for */ + + /* ensure that we have at least one credential element */ + if (creds->count < 1) { + free(creds); + return (major); + } + + /* + * fill in output parameters + * setup the actual mechs output parameter + */ + if (actual_mechs != NULL) { + gss_OID_set_desc oids; + + oids.count = creds->count; + oids.elements = creds->mechs_array; + + major = generic_gss_copy_oid_set(minor_status, &oids, actual_mechs); + if (GSS_ERROR(major)) { + (void) gss_release_cred(minor_status, + (gss_cred_id_t *)&creds); + return (major); + } + } + + if (time_rec) + *time_rec = outTime; + + + creds->loopback = creds; + *output_cred_handle = (gss_cred_id_t)creds; + return (GSS_S_COMPLETE); +} + +static OM_uint32 +val_add_cred_impersonate_name_args( + OM_uint32 *minor_status, + gss_cred_id_t input_cred_handle, + const gss_cred_id_t impersonator_cred_handle, + gss_name_t desired_name, + gss_OID desired_mech, + gss_cred_usage_t cred_usage, + OM_uint32 initiator_time_req, + OM_uint32 acceptor_time_req, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *initiator_time_rec, + OM_uint32 *acceptor_time_rec) +{ + + /* Initialize outputs. */ + + if (minor_status != NULL) + *minor_status = 0; + + if (output_cred_handle != NULL) + *output_cred_handle = GSS_C_NO_CREDENTIAL; + + if (actual_mechs != NULL) + *actual_mechs = GSS_C_NO_OID_SET; + + if (acceptor_time_rec != NULL) + *acceptor_time_rec = 0; + + if (initiator_time_rec != NULL) + *initiator_time_rec = 0; + + /* Validate arguments. */ + + if (minor_status == NULL) + return (GSS_S_CALL_INACCESSIBLE_WRITE); + + if (impersonator_cred_handle == GSS_C_NO_CREDENTIAL) + return (GSS_S_CALL_INACCESSIBLE_READ | GSS_S_NO_CRED); + + if (desired_name == GSS_C_NO_NAME) + return (GSS_S_CALL_INACCESSIBLE_READ | GSS_S_BAD_NAME); + + if (input_cred_handle == GSS_C_NO_CREDENTIAL && + output_cred_handle == NULL) + return (GSS_S_CALL_INACCESSIBLE_WRITE | GSS_S_NO_CRED); + + if (cred_usage != GSS_C_ACCEPT + && cred_usage != GSS_C_INITIATE + && cred_usage != GSS_C_BOTH) { + if (minor_status) { + *minor_status = EINVAL; + map_errcode(minor_status); + } + return GSS_S_FAILURE; + } + + return (GSS_S_COMPLETE); +} + + +/* V2 KRB5_CALLCONV */ +OM_uint32 KRB5_CALLCONV +gss_add_cred_impersonate_name(OM_uint32 *minor_status, + gss_cred_id_t input_cred_handle, + const gss_cred_id_t impersonator_cred_handle, + const gss_name_t desired_name, + const gss_OID desired_mech, + gss_cred_usage_t cred_usage, + OM_uint32 initiator_time_req, + OM_uint32 acceptor_time_req, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *initiator_time_rec, + OM_uint32 *acceptor_time_rec) +{ + OM_uint32 status, temp_minor_status; + OM_uint32 time_req, time_rec; + gss_union_name_t union_name; + gss_union_cred_t new_union_cred, union_cred; + gss_cred_id_t mech_impersonator_cred; + gss_name_t internal_name = GSS_C_NO_NAME; + gss_name_t allocated_name = GSS_C_NO_NAME; + gss_mechanism mech; + gss_cred_id_t cred = NULL; + gss_OID new_mechs_array = NULL; + gss_cred_id_t * new_cred_array = NULL; + + status = val_add_cred_impersonate_name_args(minor_status, + input_cred_handle, + impersonator_cred_handle, + desired_name, + desired_mech, + cred_usage, + initiator_time_req, + acceptor_time_req, + output_cred_handle, + actual_mechs, + initiator_time_rec, + acceptor_time_rec); + if (status != GSS_S_COMPLETE) + return (status); + + mech = gssint_get_mechanism(desired_mech); + if (!mech) + return GSS_S_BAD_MECH; + else if (!mech->gss_acquire_cred) + return (GSS_S_UNAVAILABLE); + + if (input_cred_handle == GSS_C_NO_CREDENTIAL) { + union_cred = malloc(sizeof (gss_union_cred_desc)); + if (union_cred == NULL) + return (GSS_S_FAILURE); + + (void) memset(union_cred, 0, sizeof (gss_union_cred_desc)); + + /* for default credentials we will use GSS_C_NO_NAME */ + internal_name = GSS_C_NO_NAME; + } else { + union_cred = (gss_union_cred_t)input_cred_handle; + if (gssint_get_mechanism_cred(union_cred, desired_mech) != + GSS_C_NO_CREDENTIAL) + return (GSS_S_DUPLICATE_ELEMENT); + } + + mech_impersonator_cred = + gssint_get_mechanism_cred((gss_union_cred_t)impersonator_cred_handle, + desired_mech); + if (mech_impersonator_cred == GSS_C_NO_CREDENTIAL) + return (GSS_S_NO_CRED); + + /* may need to create a mechanism specific name */ + union_name = (gss_union_name_t)desired_name; + if (union_name->mech_type && + g_OID_equal(union_name->mech_type, + &mech->mech_type)) + internal_name = union_name->mech_name; + else { + if (gssint_import_internal_name(minor_status, + &mech->mech_type, union_name, + &allocated_name) != GSS_S_COMPLETE) + return (GSS_S_BAD_NAME); + internal_name = allocated_name; + } + + if (cred_usage == GSS_C_ACCEPT) + time_req = acceptor_time_req; + else if (cred_usage == GSS_C_INITIATE) + time_req = initiator_time_req; + else if (cred_usage == GSS_C_BOTH) + time_req = (acceptor_time_req > initiator_time_req) ? + acceptor_time_req : initiator_time_req; + else + time_req = 0; + + status = mech->gss_acquire_cred_impersonate_name(minor_status, + mech_impersonator_cred, + internal_name, + time_req, + GSS_C_NULL_OID_SET, + cred_usage, + &cred, + NULL, + &time_rec); + if (status != GSS_S_COMPLETE) { + map_error(minor_status, mech); + goto errout; + } + + /* may need to set credential auxinfo strucutre */ + if (union_cred->auxinfo.creation_time == 0) { + union_cred->auxinfo.creation_time = time(NULL); + union_cred->auxinfo.time_rec = time_rec; + union_cred->auxinfo.cred_usage = cred_usage; + + /* + * we must set the name; if name is not supplied + * we must do inquire cred to get it + */ + if (internal_name == NULL) { + if (mech->gss_inquire_cred == NULL || + ((status = mech->gss_inquire_cred( + &temp_minor_status, cred, + &allocated_name, NULL, NULL, + NULL)) != GSS_S_COMPLETE)) + goto errout; + internal_name = allocated_name; + } + + if (internal_name != GSS_C_NO_NAME) { + status = mech->gss_display_name(&temp_minor_status, internal_name, + &union_cred->auxinfo.name, + &union_cred->auxinfo.name_type); + + if (status != GSS_S_COMPLETE) + goto errout; + } + } + + /* now add the new credential elements */ + new_mechs_array = (gss_OID) + malloc(sizeof (gss_OID_desc) * (union_cred->count+1)); + + new_cred_array = (gss_cred_id_t *) + malloc(sizeof (gss_cred_id_t) * (union_cred->count+1)); + + if (!new_mechs_array || !new_cred_array) { + status = GSS_S_FAILURE; + goto errout; + } + + if (acceptor_time_rec) + if (cred_usage == GSS_C_ACCEPT || cred_usage == GSS_C_BOTH) + *acceptor_time_rec = time_rec; + if (initiator_time_rec) + if (cred_usage == GSS_C_INITIATE || cred_usage == GSS_C_BOTH) + *initiator_time_rec = time_rec; + + /* + * OK, expand the mechanism array and the credential array + */ + (void) memcpy(new_mechs_array, union_cred->mechs_array, + sizeof (gss_OID_desc) * union_cred->count); + (void) memcpy(new_cred_array, union_cred->cred_array, + sizeof (gss_cred_id_t) * union_cred->count); + + new_cred_array[union_cred->count] = cred; + if ((new_mechs_array[union_cred->count].elements = + malloc(mech->mech_type.length)) == NULL) + goto errout; + + g_OID_copy(&new_mechs_array[union_cred->count], + &mech->mech_type); + + if (actual_mechs != NULL) { + gss_OID_set_desc oids; + + oids.count = union_cred->count + 1; + oids.elements = new_mechs_array; + + status = generic_gss_copy_oid_set(minor_status, &oids, actual_mechs); + if (GSS_ERROR(status)) { + free(new_mechs_array[union_cred->count].elements); + goto errout; + } + } + + if (output_cred_handle == NULL) { + free(union_cred->mechs_array); + free(union_cred->cred_array); + new_union_cred = union_cred; + } else { + new_union_cred = malloc(sizeof (gss_union_cred_desc)); + if (new_union_cred == NULL) { + free(new_mechs_array[union_cred->count].elements); + goto errout; + } + *new_union_cred = *union_cred; + *output_cred_handle = (gss_cred_id_t)new_union_cred; + } + + new_union_cred->mechs_array = new_mechs_array; + new_union_cred->cred_array = new_cred_array; + new_union_cred->count++; + new_union_cred->loopback = new_union_cred; + + /* We're done with the internal name. Free it if we allocated it. */ + + if (allocated_name) + (void) gssint_release_internal_name(&temp_minor_status, + &mech->mech_type, + &allocated_name); + + return (GSS_S_COMPLETE); + +errout: + if (new_mechs_array) + free(new_mechs_array); + if (new_cred_array) + free(new_cred_array); + + if (cred != NULL && mech->gss_release_cred) + mech->gss_release_cred(&temp_minor_status, &cred); + + if (allocated_name) + (void) gssint_release_internal_name(&temp_minor_status, + &mech->mech_type, + &allocated_name); + + if (input_cred_handle == GSS_C_NO_CREDENTIAL && union_cred) { + if (union_cred->auxinfo.name.value) + free(union_cred->auxinfo.name.value); + free(union_cred); + } + + return (status); +} diff --git a/src/lib/gssapi/mechglue/g_glue.c b/src/lib/gssapi/mechglue/g_glue.c index 5a8ea54b1..4d35819c5 100644 --- a/src/lib/gssapi/mechglue/g_glue.c +++ b/src/lib/gssapi/mechglue/g_glue.c @@ -611,25 +611,9 @@ gssint_get_mechanism_cred(union_cred, mech_type) if (union_cred == GSS_C_NO_CREDENTIAL) return GSS_C_NO_CREDENTIAL; - /* SPNEGO mechanism will again call into GSSAPI */ - if (g_OID_equal(&gss_spnego_mechanism_oid_desc, mech_type)) - return (gss_cred_id_t)union_cred; - for (i=0; i < union_cred->count; i++) { if (g_OID_equal(mech_type, &union_cred->mechs_array[i])) return union_cred->cred_array[i]; - - /* for SPNEGO, check the next-lower set of creds */ - if (g_OID_equal(&gss_spnego_mechanism_oid_desc, &union_cred->mechs_array[i])) { - gss_union_cred_t candidate_cred; - gss_cred_id_t sub_cred; - - candidate_cred = (gss_union_cred_t)union_cred->cred_array[i]; - sub_cred = gssint_get_mechanism_cred(candidate_cred, mech_type); - - if(sub_cred != GSS_C_NO_CREDENTIAL) - return sub_cred; - } } return GSS_C_NO_CREDENTIAL; } diff --git a/src/lib/gssapi/mechglue/g_initialize.c b/src/lib/gssapi/mechglue/g_initialize.c index 85fbe6321..e34b7bf0a 100644 --- a/src/lib/gssapi/mechglue/g_initialize.c +++ b/src/lib/gssapi/mechglue/g_initialize.c @@ -761,6 +761,9 @@ build_dynamicMech(void *dl, const gss_OID mech_type) GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_unwrap_iov); GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_wrap_iov_length); GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_complete_auth_token); + /* New for 1.8 */ + GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_acquire_cred_impersonate_name); + GSS_ADD_DYNAMIC_METHOD(dl, mech, gss_add_cred_impersonate_name); assert(mech_type != GSS_C_NO_OID); diff --git a/src/lib/gssapi/mechglue/g_set_context_option.c b/src/lib/gssapi/mechglue/g_set_context_option.c index 17d9e3bac..b35b36ad5 100644 --- a/src/lib/gssapi/mechglue/g_set_context_option.c +++ b/src/lib/gssapi/mechglue/g_set_context_option.c @@ -70,8 +70,8 @@ gss_set_sec_context_option (OM_uint32 *minor_status, } status = mech->gss_set_sec_context_option(minor_status, - ctx ? &internal_ctx : - &ctx->internal_ctx_id, + ctx ? &ctx->internal_ctx_id : + &internal_ctx, desired_object, value); if (status == GSS_S_COMPLETE) { diff --git a/src/lib/gssapi/mechglue/mglueP.h b/src/lib/gssapi/mechglue/mglueP.h index 001636146..46bfb9463 100644 --- a/src/lib/gssapi/mechglue/mglueP.h +++ b/src/lib/gssapi/mechglue/mglueP.h @@ -473,6 +473,37 @@ typedef struct gss_config { gss_buffer_t /* input_message_buffer */ ); + /* New for 1.8 */ + + OM_uint32 (*gss_acquire_cred_impersonate_name) + ( + OM_uint32 *, /* minor_status */ + const gss_cred_id_t, /* impersonator_cred_handle */ + const gss_name_t, /* desired_name */ + OM_uint32, /* time_req */ + const gss_OID_set, /* desired_mechs */ + gss_cred_usage_t, /* cred_usage */ + gss_cred_id_t *, /* output_cred_handle */ + gss_OID_set *, /* actual_mechs */ + OM_uint32 * /* time_rec */ + /* */); + + OM_uint32 (*gss_add_cred_impersonate_name) + ( + OM_uint32 *, /* minor_status */ + gss_cred_id_t, /* input_cred_handle */ + const gss_cred_id_t, /* impersonator_cred_handle */ + const gss_name_t, /* desired_name */ + const gss_OID, /* desired_mech */ + gss_cred_usage_t, /* cred_usage */ + OM_uint32, /* initiator_time_req */ + OM_uint32, /* acceptor_time_req */ + gss_cred_id_t *, /* output_cred_handle */ + gss_OID_set *, /* actual_mechs */ + OM_uint32 *, /* initiator_time_rec */ + OM_uint32 * /* acceptor_time_rec */ + /* */); + } *gss_mechanism; /* This structure MUST NOT be used by any code outside libgss */ diff --git a/src/lib/gssapi/spnego/gssapiP_spnego.h b/src/lib/gssapi/spnego/gssapiP_spnego.h index e1f3987cd..5e6cd5a0c 100644 --- a/src/lib/gssapi/spnego/gssapiP_spnego.h +++ b/src/lib/gssapi/spnego/gssapiP_spnego.h @@ -218,6 +218,16 @@ OM_uint32 spnego_gss_release_name gss_name_t * /* input_name */ ); +OM_uint32 spnego_gss_inquire_cred +( + OM_uint32 *, /* minor_status */ + gss_cred_id_t, /* cred_handle */ + gss_name_t *, /* name */ + OM_uint32 *, /* lifetime */ + int *, /* cred_usage */ + gss_OID_set * /* mechanisms */ +); + OM_uint32 spnego_gss_inquire_names_for_mech ( OM_uint32 *, /* minor_status */ @@ -333,6 +343,15 @@ spnego_gss_inquire_sec_context_by_oid ); OM_uint32 +spnego_gss_inquire_cred_by_oid +( + OM_uint32 *minor_status, + const gss_cred_id_t cred_handle, + const gss_OID desired_object, + gss_buffer_set_t *data_set +); + +OM_uint32 spnego_gss_set_sec_context_option ( OM_uint32 *minor_status, @@ -411,6 +430,18 @@ spnego_gss_complete_auth_token gss_buffer_t input_message_buffer ); +OM_uint32 +spnego_gss_acquire_cred_impersonate_name( + OM_uint32 *, /* minor_status */ + const gss_cred_id_t, /* impersonator_cred_handle */ + const gss_name_t, /* desired_name */ + OM_uint32, /* time_req */ + const gss_OID_set, /* desired_mechs */ + gss_cred_usage_t, /* cred_usage */ + gss_cred_id_t *, /* output_cred_handle */ + gss_OID_set *, /* actual_mechs */ + OM_uint32 *); /* time_rec */ + #ifdef __cplusplus } #endif diff --git a/src/lib/gssapi/spnego/spnego_mech.c b/src/lib/gssapi/spnego/spnego_mech.c index a2b926dc3..14b65f751 100644 --- a/src/lib/gssapi/spnego/spnego_mech.c +++ b/src/lib/gssapi/spnego/spnego_mech.c @@ -231,7 +231,7 @@ static struct gss_config spnego_mechanism = spnego_gss_display_name, spnego_gss_import_name, spnego_gss_release_name, - NULL, /* gss_inquire_cred */ + spnego_gss_inquire_cred, /* gss_inquire_cred */ NULL, /* gss_add_cred */ #ifndef LEAN_CLIENT spnego_gss_export_sec_context, /* gss_export_sec_context */ @@ -248,7 +248,7 @@ static struct gss_config spnego_mechanism = NULL, /* gss_export_name */ NULL, /* gss_store_cred */ spnego_gss_inquire_sec_context_by_oid, /* gss_inquire_sec_context_by_oid */ - NULL, /* gss_inquire_cred_by_oid */ + spnego_gss_inquire_cred_by_oid, /* gss_inquire_cred_by_oid */ spnego_gss_set_sec_context_option, /* gss_set_sec_context_option */ NULL, /* gssspi_set_cred_option */ NULL, /* gssspi_mech_invoke */ @@ -257,7 +257,9 @@ static struct gss_config spnego_mechanism = spnego_gss_wrap_iov, spnego_gss_unwrap_iov, spnego_gss_wrap_iov_length, - spnego_gss_complete_auth_token + spnego_gss_complete_auth_token, + spnego_gss_acquire_cred_impersonate_name, + NULL, /* gss_add_cred_impersonate_name */ }; #ifdef _GSS_STATIC_LINK @@ -1787,6 +1789,76 @@ spnego_gss_release_name( return (status); } +OM_uint32 +spnego_gss_inquire_cred( + OM_uint32 *minor_status, + gss_cred_id_t cred_handle, + gss_name_t *name, + OM_uint32 *lifetime, + int *cred_usage, + gss_OID_set *mechanisms) +{ + OM_uint32 status; + gss_cred_id_t creds = GSS_C_NO_CREDENTIAL; + OM_uint32 tmp_minor_status; + OM_uint32 initiator_lifetime, acceptor_lifetime; + + dsyslog("Entering inquire_cred\n"); + + /* + * To avoid infinite recursion, if GSS_C_NO_CREDENTIAL is + * supplied we call gss_inquire_cred_by_mech() on the + * first non-SPNEGO mechanism. + */ + if (cred_handle == GSS_C_NO_CREDENTIAL) { + status = get_available_mechs(minor_status, + GSS_C_NO_NAME, + GSS_C_BOTH, + &creds, + mechanisms); + if (status != GSS_S_COMPLETE) { + dsyslog("Leaving inquire_cred\n"); + return (status); + } + + if ((*mechanisms)->count == 0) { + gss_release_cred(&tmp_minor_status, &creds); + gss_release_oid_set(&tmp_minor_status, mechanisms); + dsyslog("Leaving inquire_cred\n"); + return (GSS_S_DEFECTIVE_CREDENTIAL); + } + + assert((*mechanisms)->elements != NULL); + + status = gss_inquire_cred_by_mech(minor_status, + creds, + &(*mechanisms)->elements[0], + name, + &initiator_lifetime, + &acceptor_lifetime, + cred_usage); + if (status != GSS_S_COMPLETE) { + gss_release_cred(&tmp_minor_status, &creds); + dsyslog("Leaving inquire_cred\n"); + return (status); + } + + if (lifetime != NULL) + *lifetime = (*cred_usage == GSS_C_ACCEPT) ? + acceptor_lifetime : initiator_lifetime; + + gss_release_cred(&tmp_minor_status, &creds); + } else { + status = gss_inquire_cred(minor_status, cred_handle, + name, lifetime, + cred_usage, mechanisms); + } + + dsyslog("Leaving inquire_cred\n"); + + return (status); +} + /*ARGSUSED*/ OM_uint32 spnego_gss_compare_name( @@ -1942,6 +2014,9 @@ spnego_gss_delete_sec_context( */ if (*ctx != NULL && (*ctx)->magic_num == SPNEGO_MAGIC_ID) { + (void) gss_delete_sec_context(minor_status, + &(*ctx)->ctx_handle, + output_token); (void) release_spnego_ctx(ctx); } else { ret = gss_delete_sec_context(minor_status, @@ -2088,6 +2163,21 @@ spnego_gss_inquire_sec_context_by_oid( } OM_uint32 +spnego_gss_inquire_cred_by_oid( + OM_uint32 *minor_status, + const gss_cred_id_t cred_handle, + const gss_OID desired_object, + gss_buffer_set_t *data_set) +{ + OM_uint32 ret; + ret = gss_inquire_cred_by_oid(minor_status, + cred_handle, + desired_object, + data_set); + return (ret); +} + +OM_uint32 spnego_gss_set_sec_context_option( OM_uint32 *minor_status, gss_ctx_id_t *context_handle, @@ -2217,6 +2307,53 @@ spnego_gss_complete_auth_token( return (ret); } +OM_uint32 +spnego_gss_acquire_cred_impersonate_name(OM_uint32 *minor_status, + const gss_cred_id_t impersonator_cred_handle, + gss_name_t desired_name, + OM_uint32 time_req, + gss_OID_set desired_mechs, + gss_cred_usage_t cred_usage, + gss_cred_id_t *output_cred_handle, + gss_OID_set *actual_mechs, + OM_uint32 *time_rec) +{ + OM_uint32 status; + gss_OID_set amechs = GSS_C_NULL_OID_SET; + + dsyslog("Entering spnego_gss_acquire_cred_impersonate_name\n"); + + if (actual_mechs) + *actual_mechs = NULL; + + if (time_rec) + *time_rec = 0; + + if (desired_mechs == GSS_C_NO_OID_SET) { + status = gss_inquire_cred(minor_status, + impersonator_cred_handle, + NULL, NULL, + NULL, &amechs); + if (status != GSS_S_COMPLETE) + return status; + + desired_mechs = amechs; + } + + status = gss_acquire_cred_impersonate_name(minor_status, + impersonator_cred_handle, + desired_name, time_req, + desired_mechs, cred_usage, + output_cred_handle, actual_mechs, + time_rec); + + if (amechs != GSS_C_NULL_OID_SET) + (void) gss_release_oid_set(minor_status, &amechs); + + dsyslog("Leaving spnego_gss_acquire_cred_impersonate_name\n"); + return (status); +} + /* * We will release everything but the ctx_handle so that it * can be passed back to init/accept context. This routine should diff --git a/src/lib/kadm5/str_conv.c b/src/lib/kadm5/str_conv.c index 2bd99adbc..51637f7de 100644 --- a/src/lib/kadm5/str_conv.c +++ b/src/lib/kadm5/str_conv.c @@ -78,6 +78,8 @@ static const char flags_pwchange_in[] = "pwchange"; static const char flags_service_in[] = "service"; static const char flags_pwsvc_in[] = "pwservice"; static const char flags_md5_in[] = "md5"; +static const char flags_ok_to_auth_as_delegate_in[] = "ok-to-auth-as-delegate"; +static const char flags_no_auth_data_required_in[] = "no-auth-data-required"; static const char flags_pdate_out[] = "Not Postdateable"; static const char flags_fwd_out[] = "Not Forwardable"; static const char flags_tgtbased_out[] = "No TGT-based requests"; @@ -85,13 +87,15 @@ static const char flags_renew_out[] = "Not renewable"; static const char flags_proxy_out[] = "Not proxiable"; static const char flags_dup_skey_out[] = "No DUP_SKEY requests"; static const char flags_tickets_out[] = "All Tickets Disallowed"; -static const char flags_preauth_out[] = "Preauthorization required"; -static const char flags_hwauth_out[] = "HW Authorization required"; +static const char flags_preauth_out[] = "Preauthentication required"; +static const char flags_hwauth_out[] = "HW authentication required"; static const char flags_ok_as_delegate_out[] = "OK as Delegate"; static const char flags_pwchange_out[] = "Password Change required"; static const char flags_service_out[] = "Service Disabled"; static const char flags_pwsvc_out[] = "Password Changing Service"; static const char flags_md5_out[] = "RSA-MD5 supported"; +static const char flags_ok_to_auth_as_delegate_out[] = "Protocol transition with delegation allowed"; +static const char flags_no_auth_data_required_out[] = "No authorization data required"; static const char flags_default_neg[] = "-"; static const char flags_default_sep[] = " "; @@ -115,7 +119,9 @@ static const struct flags_lookup_entry flags_table[] = { { KRB5_KDB_REQUIRES_PWCHANGE, 1, flags_pwchange_in, flags_pwchange_out}, { KRB5_KDB_DISALLOW_SVR, 0, flags_service_in, flags_service_out }, { KRB5_KDB_PWCHANGE_SERVICE, 1, flags_pwsvc_in, flags_pwsvc_out }, -{ KRB5_KDB_SUPPORT_DESMD5, 1, flags_md5_in, flags_md5_out } +{ KRB5_KDB_SUPPORT_DESMD5, 1, flags_md5_in, flags_md5_out }, +{ KRB5_KDB_OK_TO_AUTH_AS_DELEGATE, 1, flags_ok_to_auth_as_delegate_in, flags_ok_to_auth_as_delegate_out }, +{ KRB5_KDB_NO_AUTH_DATA_REQUIRED, 1, flags_no_auth_data_required_in, flags_no_auth_data_required_out } }; static const int flags_table_nents = sizeof(flags_table)/ sizeof(flags_table[0]); diff --git a/src/lib/krb5/asn.1/asn1_k_decode.c b/src/lib/krb5/asn.1/asn1_k_decode.c index 1917d8974..b1b09371b 100644 --- a/src/lib/krb5/asn.1/asn1_k_decode.c +++ b/src/lib/krb5/asn.1/asn1_k_decode.c @@ -1616,6 +1616,47 @@ error_out: return retval; } +asn1_error_code asn1_decode_s4u_userid(asn1buf *buf, krb5_s4u_userid *val) +{ + setup(); + val->nonce = 0; + val->user = NULL; + val->subject_cert.data = NULL; + val->options = 0; + { begin_structure(); + get_field(val->nonce,0,asn1_decode_int32); + alloc_principal(val->user); + opt_field(val->user,1,asn1_decode_principal_name,0); + get_field(val->user,2,asn1_decode_realm); + opt_lenfield(val->subject_cert.length,val->subject_cert.data,3,asn1_decode_charstring); + opt_field(val->options,4,asn1_decode_krb5_flags,0); + end_structure(); + } + return 0; +error_out: + krb5_free_principal(NULL, val->user); + krb5_free_data_contents(NULL, &val->subject_cert); + val->user = NULL; + val->subject_cert.data = NULL; + return retval; +} + +asn1_error_code asn1_decode_pa_s4u_x509_user(asn1buf *buf, krb5_pa_s4u_x509_user *val) +{ + setup(); + val->cksum.contents = NULL; + { begin_structure(); + get_field(val->user_id,0,asn1_decode_s4u_userid); + get_field(val->cksum,1,asn1_decode_checksum); + end_structure(); + } + return 0; +error_out: + krb5_free_s4u_userid_contents(NULL, &val->user_id); + krb5_free_checksum_contents(NULL, &val->cksum); + return retval; +} + asn1_error_code asn1_decode_pa_pac_req(asn1buf *buf, krb5_pa_pac_req *val) { setup(); diff --git a/src/lib/krb5/asn.1/asn1_k_decode.h b/src/lib/krb5/asn.1/asn1_k_decode.h index 7444443ba..fc62c8f4e 100644 --- a/src/lib/krb5/asn.1/asn1_k_decode.h +++ b/src/lib/krb5/asn.1/asn1_k_decode.h @@ -263,6 +263,10 @@ asn1_error_code asn1_decode_setpw_req (asn1buf *buf, krb5_data *rep, krb5_principal *principal); asn1_error_code asn1_decode_pa_for_user (asn1buf *buf, krb5_pa_for_user *val); +asn1_error_code asn1_decode_s4u_userid + (asn1buf *buf, krb5_s4u_userid *val); +asn1_error_code asn1_decode_pa_s4u_x509_user + (asn1buf *buf, krb5_pa_s4u_x509_user *val); asn1_error_code asn1_decode_pa_pac_req (asn1buf *buf, krb5_pa_pac_req *val); diff --git a/src/lib/krb5/asn.1/asn1_k_encode.c b/src/lib/krb5/asn.1/asn1_k_encode.c index ed01b7560..cd63ffbb9 100644 --- a/src/lib/krb5/asn.1/asn1_k_encode.c +++ b/src/lib/krb5/asn.1/asn1_k_encode.c @@ -263,6 +263,8 @@ static unsigned int optional_enc_kdc_rep_part(const void *p) optional |= (1u << 8); if (val->caddrs != NULL && val->caddrs[0] != NULL) optional |= (1u << 11); + if (val->enc_padata != NULL) + optional |= (1u << 12); return optional; } @@ -1147,6 +1149,36 @@ static const struct field_info pa_for_user_fields[] = { DEFSEQTYPE(pa_for_user, krb5_pa_for_user, pa_for_user_fields, 0); +/* [MS-SFU] Section 2.2.2. */ +static const struct field_info s4u_userid_fields[] = { + FIELDOF_NORM(krb5_s4u_userid, int32, nonce, 0), + FIELDOF_OPT(krb5_s4u_userid, principal, user, 1, 1), + FIELDOF_NORM(krb5_s4u_userid, realm_of_principal, user, 2), + FIELDOF_OPT(krb5_s4u_userid, ostring_data, subject_cert, 3, 3), + FIELDOF_OPT(krb5_s4u_userid, krb5_flags, options, 4, 4), +}; + +static unsigned int s4u_userid_optional (const void *p) { + const krb5_s4u_userid *val = p; + unsigned int optional = 0; + if (val->user != NULL && val->user->length != 0) + optional |= (1u)<<1; + if (val->subject_cert.length != 0) + optional |= (1u)<<3; + if (val->options != 0) + optional |= (1u)<<4; + return optional; +} + +DEFSEQTYPE(s4u_userid, krb5_s4u_userid, s4u_userid_fields, s4u_userid_optional); + +static const struct field_info pa_s4u_x509_user_fields[] = { + FIELDOF_NORM(krb5_pa_s4u_x509_user, s4u_userid, user_id, 0), + FIELDOF_NORM(krb5_pa_s4u_x509_user, checksum, cksum, 1), +}; + +DEFSEQTYPE(pa_s4u_x509_user, krb5_pa_s4u_x509_user, pa_s4u_x509_user_fields, 0); + /* draft-ietf-krb-wg-kerberos-referrals Appendix A. */ static const struct field_info pa_svr_referral_data_fields[] = { FIELDOF_NORM(krb5_pa_svr_referral_data, realm_of_principal, principal, 0), @@ -1323,6 +1355,8 @@ MAKE_FULL_ENCODER(encode_krb5_predicted_sam_response, predicted_sam_response); MAKE_FULL_ENCODER(encode_krb5_setpw_req, setpw_req); MAKE_FULL_ENCODER(encode_krb5_pa_for_user, pa_for_user); +MAKE_FULL_ENCODER(encode_krb5_s4u_userid, s4u_userid); +MAKE_FULL_ENCODER(encode_krb5_pa_s4u_x509_user, pa_s4u_x509_user); MAKE_FULL_ENCODER(encode_krb5_pa_svr_referral_data, pa_svr_referral_data); MAKE_FULL_ENCODER(encode_krb5_pa_server_referral_data, pa_server_referral_data); MAKE_FULL_ENCODER(encode_krb5_etype_list, etype_list); diff --git a/src/lib/krb5/asn.1/krb5_decode.c b/src/lib/krb5/asn.1/krb5_decode.c index 7a08ec888..a2e9c0a4d 100644 --- a/src/lib/krb5/asn.1/krb5_decode.c +++ b/src/lib/krb5/asn.1/krb5_decode.c @@ -1061,6 +1061,18 @@ decode_krb5_pa_for_user(const krb5_data *code, krb5_pa_for_user **repptr) } krb5_error_code +decode_krb5_pa_s4u_x509_user(const krb5_data *code, krb5_pa_s4u_x509_user **repptr) +{ + setup_buf_only(krb5_pa_s4u_x509_user *); + alloc_field(rep); + + retval = asn1_decode_pa_s4u_x509_user(&buf, rep); + if (retval) clean_return(retval); + + cleanup(free); +} + +krb5_error_code decode_krb5_pa_pac_req(const krb5_data *code, krb5_pa_pac_req **repptr) { setup_buf_only(krb5_pa_pac_req *); diff --git a/src/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in index 9715e6dfc..8b8f6d2db 100644 --- a/src/lib/krb5/krb/Makefile.in +++ b/src/lib/krb5/krb/Makefile.in @@ -79,6 +79,7 @@ STLIBOBJS= \ rd_req_dec.o \ rd_safe.o \ recvauth.o \ + s4u_creds.o \ sendauth.o \ send_tgs.o \ ser_actx.o \ @@ -167,6 +168,7 @@ OBJS= $(OUTPRE)addr_comp.$(OBJEXT) \ $(OUTPRE)rd_req_dec.$(OBJEXT) \ $(OUTPRE)rd_safe.$(OBJEXT) \ $(OUTPRE)recvauth.$(OBJEXT) \ + $(OUTPRE)s4u_creds.$(OBJEXT) \ $(OUTPRE)sendauth.$(OBJEXT) \ $(OUTPRE)send_tgs.$(OBJEXT) \ $(OUTPRE)ser_actx.$(OBJEXT) \ @@ -256,6 +258,7 @@ SRCS= $(srcdir)/addr_comp.c \ $(srcdir)/rd_req_dec.c \ $(srcdir)/rd_safe.c \ $(srcdir)/recvauth.c \ + $(srcdir)/s4u_creds.c \ $(srcdir)/sendauth.c \ $(srcdir)/send_tgs.c \ $(srcdir)/ser_actx.c \ diff --git a/src/lib/krb5/krb/gc_frm_kdc.c b/src/lib/krb5/krb/gc_frm_kdc.c index 3098e8e13..b3144c84e 100644 --- a/src/lib/krb5/krb/gc_frm_kdc.c +++ b/src/lib/krb5/krb/gc_frm_kdc.c @@ -1007,6 +1007,11 @@ krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache, DUMP_PRINC("gc_from_kdc: server as requested", supplied_server); + if (in_cred->second_ticket.length != 0 && + (kdcopt & KDC_OPT_CNAME_IN_ADDL_TKT) == 0) { + kdcopt |= KDC_OPT_ENC_TKT_IN_SKEY; + } + /* * Try requesting a service ticket from our local KDC with referrals * turned on. If the first referral succeeds, follow a referral-only @@ -1028,9 +1033,7 @@ krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache, retval = krb5_get_cred_via_tkt(context, tgtptr, KDC_OPT_CANONICALIZE | FLAGS2OPTS(tgtptr->ticket_flags) | - kdcopt | - (in_cred->second_ticket.length ? - KDC_OPT_ENC_TKT_IN_SKEY : 0), + kdcopt, tgtptr->addresses, in_cred, out_cred); if (retval) { DPRINTF(("gc_from_kdc: referral TGS-REQ request failed: <%s>\n", @@ -1048,9 +1051,7 @@ krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache, "retrying without option.\n", referral_count + 1)); retval = krb5_get_cred_via_tkt(context, tgtptr, FLAGS2OPTS(tgtptr->ticket_flags) | - kdcopt | - (in_cred->second_ticket.length ? - KDC_OPT_ENC_TKT_IN_SKEY : 0), + kdcopt, tgtptr->addresses, in_cred, out_cred); /* Whether or not that succeeded, we're done. */ @@ -1090,9 +1091,7 @@ krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache, retval = krb5_get_cred_via_tkt(context, tgtptr, KDC_OPT_CANONICALIZE | FLAGS2OPTS(tgtptr->ticket_flags) | - kdcopt | - (in_cred->second_ticket.length ? - KDC_OPT_ENC_TKT_IN_SKEY : 0), + kdcopt, tgtptr->addresses, in_cred, out_cred); goto cleanup; @@ -1257,9 +1256,7 @@ krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache, context->use_conf_ktypes = old_use_conf_ktypes; retval = krb5_get_cred_via_tkt(context, tgtptr, FLAGS2OPTS(tgtptr->ticket_flags) | - kdcopt | - (in_cred->second_ticket.length ? - KDC_OPT_ENC_TKT_IN_SKEY : 0), + kdcopt, tgtptr->addresses, in_cred, out_cred); cleanup: diff --git a/src/lib/krb5/krb/gc_via_tkt.c b/src/lib/krb5/krb/gc_via_tkt.c index 83c8026fc..273655ab5 100644 --- a/src/lib/krb5/krb/gc_via_tkt.c +++ b/src/lib/krb5/krb/gc_via_tkt.c @@ -1,7 +1,7 @@ /* * lib/krb5/krb/gc_via_tgt.c * - * Copyright 1990,1991,2007,2008 by the Massachusetts Institute of Technology. + * Copyright 1990,1991,2007-2009 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may @@ -159,12 +159,34 @@ krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt, krb5_flags kdcoptions, krb5_address *const *address, krb5_creds *in_cred, krb5_creds **out_cred) { + return krb5_get_cred_via_tkt_ext (context, tkt, + kdcoptions, address, + NULL, in_cred, NULL, NULL, + NULL, NULL, out_cred, NULL); +} + +krb5_error_code +krb5_get_cred_via_tkt_ext (krb5_context context, krb5_creds *tkt, + krb5_flags kdcoptions, krb5_address *const *address, + krb5_pa_data **in_padata, + krb5_creds *in_cred, + krb5_error_code (*pacb_fct)(krb5_context, + krb5_keyblock *, + krb5_kdc_req *, + void *), + void *pacb_data, + krb5_pa_data ***out_padata, + krb5_pa_data ***out_enc_padata, + krb5_creds **out_cred, + krb5_keyblock **out_subkey) +{ krb5_error_code retval; krb5_kdc_rep *dec_rep; krb5_error *err_reply; krb5_response tgsrep; krb5_enctype *enctypes = 0; krb5_keyblock *subkey = NULL; + krb5_boolean s4u2self = FALSE, second_tkt; #ifdef DEBUG_REFERRALS printf("krb5_get_cred_via_tkt starting; referral flag is %s\n", kdcoptions&KDC_OPT_CANONICALIZE?"on":"off"); @@ -179,10 +201,13 @@ krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt, if (!tkt->ticket.length) return KRB5_NO_TKT_SUPPLIED; - if ((kdcoptions & KDC_OPT_ENC_TKT_IN_SKEY) && - (!in_cred->second_ticket.length)) + second_tkt = ((kdcoptions & (KDC_OPT_ENC_TKT_IN_SKEY | KDC_OPT_CNAME_IN_ADDL_TKT)) != 0); + + if (second_tkt && !in_cred->second_ticket.length) return(KRB5_NO_2ND_TKT); + s4u2self = krb5int_find_pa_data(context, in_padata, KRB5_PADATA_S4U_X509_USER) || + krb5int_find_pa_data(context, in_padata, KRB5_PADATA_FOR_USER); /* check if we have the right TGT */ /* tkt->server must be equal to */ @@ -210,13 +235,12 @@ krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt, enctypes[0] = in_cred->keyblock.enctype; enctypes[1] = 0; } - + retval = krb5int_send_tgs(context, kdcoptions, &in_cred->times, enctypes, in_cred->server, address, in_cred->authdata, - 0, /* no padata */ - (kdcoptions & KDC_OPT_ENC_TKT_IN_SKEY) ? - &in_cred->second_ticket : NULL, - tkt, &tgsrep, &subkey); + in_padata, + second_tkt ? &in_cred->second_ticket : NULL, + tkt, pacb_fct, pacb_data, &tgsrep, &subkey); if (enctypes) free(enctypes); if (retval) { @@ -318,8 +342,17 @@ krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt, /* make sure the response hasn't been tampered with..... */ retval = 0; - if (!krb5_principal_compare(context, dec_rep->client, tkt->client)) - retval = KRB5_KDCREP_MODIFIED; + if (s4u2self && !IS_TGS_PRINC(context, dec_rep->ticket->server)) { + /* Final hop, check whether KDC supports S4U2Self */ + if (krb5_principal_compare(context, dec_rep->client, in_cred->server)) + retval = KRB5KDC_ERR_PADATA_TYPE_NOSUPP; + } else if ((kdcoptions & KDC_OPT_CNAME_IN_ADDL_TKT) == 0) { + /* XXX for constrained delegation this check must be performed by caller + * as we don't have access to the key to decrypt the evidence ticket. + */ + if (!krb5_principal_compare(context, dec_rep->client, tkt->client)) + retval = KRB5_KDCREP_MODIFIED; + } if (retval == 0) retval = check_reply_server(context, kdcoptions, in_cred, dec_rep); @@ -356,13 +389,26 @@ krb5_get_cred_via_tkt (krb5_context context, krb5_creds *tkt, retval = KRB5_KDCREP_SKEW; goto error_3; } + + if (out_padata != NULL) { + *out_padata = dec_rep->padata; + dec_rep->padata = NULL; + } + if (out_enc_padata != NULL) { + *out_enc_padata = dec_rep->enc_part2->enc_padata; + dec_rep->enc_part2->enc_padata = NULL; + } retval = krb5_kdcrep2creds(context, dec_rep, address, &in_cred->second_ticket, out_cred); error_3:; - if (subkey != NULL) - krb5_free_keyblock(context, subkey); + if (subkey != NULL) { + if (retval == 0 && out_subkey != NULL) + *out_subkey = subkey; + else + krb5_free_keyblock(context, subkey); + } memset(dec_rep->enc_part2->session->contents, 0, dec_rep->enc_part2->session->length); diff --git a/src/lib/krb5/krb/get_creds.c b/src/lib/krb5/krb/get_creds.c index c02ddedc6..dad3e1a91 100644 --- a/src/lib/krb5/krb/get_creds.c +++ b/src/lib/krb5/krb/get_creds.c @@ -46,7 +46,7 @@ #include "k5-int.h" #include "int-proto.h" -static krb5_error_code +krb5_error_code krb5_get_credentials_core(krb5_context context, krb5_flags options, krb5_creds *in_creds, krb5_creds *mcreds, krb5_flags *fields) @@ -87,11 +87,14 @@ krb5_get_credentials_core(krb5_context context, krb5_flags options, if (ret) return ret; } - if (options & KRB5_GC_USER_USER) { + if (options & (KRB5_GC_USER_USER | KRB5_GC_CONSTRAINED_DELEGATION)) { /* also match on identical 2nd tkt and tkt encrypted in a session key */ - *fields |= KRB5_TC_MATCH_2ND_TKT|KRB5_TC_MATCH_IS_SKEY; - mcreds->is_skey = TRUE; + *fields |= KRB5_TC_MATCH_2ND_TKT; + if (options & KRB5_GC_USER_USER) { + *fields |= KRB5_TC_MATCH_IS_SKEY; + mcreds->is_skey = TRUE; + } mcreds->second_ticket = in_creds->second_ticket; if (!in_creds->second_ticket.length) return KRB5_NO_2ND_TKT; @@ -113,25 +116,35 @@ krb5_get_credentials(krb5_context context, krb5_flags options, int not_ktype; int kdcopt = 0; - retval = krb5_get_credentials_core(context, options, - in_creds, - &mcreds, &fields); + if ((options & KRB5_GC_CONSTRAINED_DELEGATION) == 0) { + retval = krb5_get_credentials_core(context, options, + in_creds, + &mcreds, &fields); - if (retval) return retval; + if (retval) + return retval; - if ((ncreds = (krb5_creds *)malloc(sizeof(krb5_creds))) == NULL) - return ENOMEM; + if ((ncreds = (krb5_creds *)malloc(sizeof(krb5_creds))) == NULL) + return ENOMEM; - memset(ncreds, 0, sizeof(krb5_creds)); - ncreds->magic = KV5M_CREDS; + memset(ncreds, 0, sizeof(krb5_creds)); + ncreds->magic = KV5M_CREDS; - /* The caller is now responsible for cleaning up in_creds */ - if ((retval = krb5_cc_retrieve_cred(context, ccache, fields, &mcreds, - ncreds))) { - free(ncreds); - ncreds = in_creds; + /* The caller is now responsible for cleaning up in_creds */ + if ((retval = krb5_cc_retrieve_cred(context, ccache, fields, &mcreds, + ncreds))) { + free(ncreds); + ncreds = in_creds; + } else { + *out_creds = ncreds; + } } else { - *out_creds = ncreds; + /* + * To do this usefully for constrained delegation, we would + * need to look inside second_ticket, which we can't do. + */ + ncreds = in_creds; + retval = KRB5_CC_NOTFOUND; } if ((retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) @@ -145,6 +158,15 @@ krb5_get_credentials(krb5_context context, krb5_flags options, if (options & KRB5_GC_CANONICALIZE) kdcopt |= KDC_OPT_CANONICALIZE; + if (options & KRB5_GC_FORWARDABLE) + kdcopt |= KDC_OPT_FORWARDABLE; + if (options & KRB5_GC_NO_TRANSIT_CHECK) + kdcopt |= KDC_OPT_DISABLE_TRANSITED_CHECK; + if (options & KRB5_GC_CONSTRAINED_DELEGATION) { + if (options & KRB5_GC_USER_USER) + return EINVAL; + kdcopt |= KDC_OPT_FORWARDABLE | KDC_OPT_CNAME_IN_ADDL_TKT; + } retval = krb5_get_cred_from_kdc_opt(context, ccache, ncreds, out_creds, &tgts, kdcopt); @@ -160,6 +182,13 @@ krb5_get_credentials(krb5_context context, krb5_flags options, } krb5_free_tgt_creds(context, tgts); } + if (!retval && (options & KRB5_GC_CONSTRAINED_DELEGATION)) { + if (((*out_creds)->ticket_flags & TKT_FLG_FORWARDABLE) == 0) { + retval = KRB5_TKT_NOT_FORWARDABLE; + krb5_free_creds(context, *out_creds); + *out_creds = NULL; + } + } /* * Translate KRB5_CC_NOTFOUND if we previously got * KRB5_CC_NOT_KTYPE from krb5_cc_retrieve_cred(), in order to @@ -175,7 +204,7 @@ krb5_get_credentials(krb5_context context, krb5_flags options, && not_ktype) retval = KRB5_CC_NOT_KTYPE; - if (!retval) { + if (!retval && (options & KRB5_GC_NO_STORE) == 0) { /* the purpose of the krb5_get_credentials call is to * obtain a set of credentials for the caller. the * krb5_cc_store_cred() call is to optimize performance @@ -184,6 +213,7 @@ krb5_get_credentials(krb5_context context, krb5_flags options, */ krb5_cc_store_cred(context, ccache, *out_creds); } + return retval; } @@ -337,3 +367,4 @@ krb5_get_renewed_creds(krb5_context context, krb5_creds *creds, krb5_principal c return(krb5_validate_or_renew_creds(context, creds, client, ccache, in_tkt_service, 0)); } + diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c index 018676dbe..63594ddfd 100644 --- a/src/lib/krb5/krb/get_in_tkt.c +++ b/src/lib/krb5/krb/get_in_tkt.c @@ -493,6 +493,55 @@ static const krb5_enctype get_in_tkt_enctypes[] = { 0 }; +static krb5_error_code +rewrite_server_realm(krb5_context context, + krb5_const_principal old_server, + const krb5_data *realm, + krb5_boolean tgs, + krb5_principal *server) +{ + krb5_error_code retval; + + assert(*server == NULL); + + retval = krb5_copy_principal(context, old_server, server); + if (retval) + return retval; + + krb5_free_data_contents(context, &(*server)->realm); + (*server)->realm.data = NULL; + + retval = krb5int_copy_data_contents(context, realm, &(*server)->realm); + if (retval) + goto cleanup; + + if (tgs) { + krb5_free_data_contents(context, &(*server)->data[1]); + (*server)->data[1].data = NULL; + + retval = krb5int_copy_data_contents(context, realm, &(*server)->data[1]); + if (retval) + goto cleanup; + } + +cleanup: + if (retval) { + krb5_free_principal(context, *server); + *server = NULL; + } + + return retval; +} + +static inline int +tgt_is_local_realm(krb5_creds *tgt) +{ + return (tgt->server->length == 2 + && data_eq_string(tgt->server->data[0], KRB5_TGS_NAME) + && data_eq(tgt->server->data[1], tgt->client->realm) + && data_eq(tgt->server->realm, tgt->client->realm)); +} + krb5_error_code KRB5_CALLCONV krb5_get_in_tkt(krb5_context context, krb5_flags options, @@ -521,6 +570,8 @@ krb5_get_in_tkt(krb5_context context, int use_master = 0; int referral_count = 0; krb5_principal_data referred_client; + krb5_principal referred_server = NULL; + krb5_boolean is_tgt_req; #if APPLE_PKINIT inTktDebug("krb5_get_in_tkt top\n"); @@ -616,6 +667,8 @@ krb5_get_in_tkt(krb5_context context, goto cleanup; } + is_tgt_req = tgt_is_local_realm(creds); + while (1) { if (loopcount++ > MAX_IN_TKT_LOOPS) { retval = KRB5_GET_IN_TKT_LOOP; @@ -687,6 +740,21 @@ krb5_get_in_tkt(krb5_context context, if (retval) goto cleanup; request.client = &referred_client; + + if (referred_server != NULL) { + krb5_free_principal(context, referred_server); + referred_server = NULL; + } + + retval = rewrite_server_realm(context, + creds->server, + &referred_client.realm, + is_tgt_req, + &referred_server); + if (retval) + goto cleanup; + request.server = referred_server; + continue; } else { retval = (krb5_error_code) err_reply->error @@ -739,6 +807,8 @@ cleanup: } if (referred_client.realm.data) krb5_free_data_contents(context, &referred_client.realm); + if (referred_server) + krb5_free_principal(context, referred_server); return (retval); } @@ -939,6 +1009,52 @@ sort_krb5_padata_sequence(krb5_context context, krb5_data *realm, return 0; } +static krb5_error_code +build_in_tkt_name(krb5_context context, + char *in_tkt_service, + krb5_const_principal client, + krb5_principal *server) +{ + krb5_error_code ret; + + *server = NULL; + + if (in_tkt_service) { + /* this is ugly, because so are the data structures involved. I'm + in the library, so I'm going to manipulate the data structures + directly, otherwise, it will be worse. */ + + if ((ret = krb5_parse_name(context, in_tkt_service, server))) + return ret; + + /* stuff the client realm into the server principal. + realloc if necessary */ + if ((*server)->realm.length < client->realm.length) { + char *p = realloc((*server)->realm.data, + client->realm.length); + if (p == NULL) { + krb5_free_principal(context, *server); + *server = NULL; + return ENOMEM; + } + (*server)->realm.data = p; + } + + (*server)->realm.length = client->realm.length; + memcpy((*server)->realm.data, client->realm.data, client->realm.length); + } else { + ret = krb5_build_principal_ext(context, server, + client->realm.length, + client->realm.data, + KRB5_TGS_NAME_SIZE, + KRB5_TGS_NAME, + client->realm.length, + client->realm.data, + 0); + } + return ret; +} + krb5_error_code KRB5_CALLCONV krb5_get_init_creds(krb5_context context, krb5_creds *creds, @@ -1125,41 +1241,9 @@ krb5_get_init_creds(krb5_context context, client->type == KRB5_NT_ENTERPRISE_PRINCIPAL; /* service */ - - if (in_tkt_service) { - /* this is ugly, because so are the data structures involved. I'm - in the library, so I'm going to manipulate the data structures - directly, otherwise, it will be worse. */ - - if ((ret = krb5_parse_name(context, in_tkt_service, &request.server))) - goto cleanup; - - /* stuff the client realm into the server principal. - realloc if necessary */ - if (request.server->realm.length < request.client->realm.length) { - char *p = realloc(request.server->realm.data, - request.client->realm.length); - if (p == NULL) { - ret = ENOMEM; - goto cleanup; - } - request.server->realm.data = p; - } - - request.server->realm.length = request.client->realm.length; - memcpy(request.server->realm.data, request.client->realm.data, - request.client->realm.length); - } else { - if ((ret = krb5_build_principal_ext(context, &request.server, - request.client->realm.length, - request.client->realm.data, - KRB5_TGS_NAME_SIZE, - KRB5_TGS_NAME, - request.client->realm.length, - request.client->realm.data, - 0))) - goto cleanup; - } + if ((ret = build_in_tkt_name(context, in_tkt_service, + request.client, &request.server))) + goto cleanup; krb5_preauth_request_context_init(context); @@ -1337,8 +1421,10 @@ krb5_get_init_creds(krb5_context context, } preauth_to_use = out_padata; out_padata = NULL; - krb5_free_error(context, err_reply); - err_reply = NULL; + if (err_reply->error == KDC_ERR_PREAUTH_REQUIRED) { + krb5_free_error(context, err_reply); + err_reply = NULL; + } ret = sort_krb5_padata_sequence(context, &request.server->realm, preauth_to_use); @@ -1365,6 +1451,14 @@ krb5_get_init_creds(krb5_context context, if (ret) goto cleanup; request.client = &referred_client; + + krb5_free_principal(context, request.server); + request.server = NULL; + + ret = build_in_tkt_name(context, in_tkt_service, + request.client, &request.server); + if (ret) + goto cleanup; } else { if (retry) { /* continue to next iteration */ diff --git a/src/lib/krb5/krb/int-proto.h b/src/lib/krb5/krb/int-proto.h index b81fe2566..cc0c9f2de 100644 --- a/src/lib/krb5/krb/int-proto.h +++ b/src/lib/krb5/krb/int-proto.h @@ -59,11 +59,31 @@ 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); +krb5_error_code +krb5_get_credentials_core(krb5_context context, krb5_flags options, + krb5_creds *in_creds, krb5_creds *mcreds, + krb5_flags *fields); + #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)) +krb5_error_code +krb5_get_cred_via_tkt_ext (krb5_context context, krb5_creds *tkt, + krb5_flags kdcoptions, krb5_address *const *address, + krb5_pa_data **in_padata, + krb5_creds *in_cred, + krb5_error_code (*gcvt_fct)(krb5_context, + krb5_keyblock *, + krb5_kdc_req *, + void *), + void *gcvt_data, + krb5_pa_data ***out_padata, + krb5_pa_data ***enc_padata, + krb5_creds **out_cred, + krb5_keyblock **out_subkey); + #endif /* KRB5_INT_FUNC_PROTO__ */ diff --git a/src/lib/krb5/krb/kfree.c b/src/lib/krb5/krb/kfree.c index bec9a61bf..8cef95431 100644 --- a/src/lib/krb5/krb/kfree.c +++ b/src/lib/krb5/krb/kfree.c @@ -170,7 +170,7 @@ krb5_free_checksum_contents(krb5_context context, register krb5_checksum *val) if (val == NULL) return; free(val->contents); - val->contents = 0; + val->contents = NULL; } void KRB5_CALLCONV @@ -297,6 +297,7 @@ krb5_free_enc_kdc_rep_part(krb5_context context, register krb5_enc_kdc_rep_part krb5_free_last_req(context, val->last_req); krb5_free_principal(context, val->server); krb5_free_addresses(context, val->caddrs); + krb5_free_pa_data(context, val->enc_padata); free(val); } @@ -755,6 +756,30 @@ krb5_free_pa_for_user(krb5_context context, krb5_pa_for_user *req) } void KRB5_CALLCONV +krb5_free_s4u_userid_contents(krb5_context context, krb5_s4u_userid *user_id) +{ + if (user_id == NULL) + return; + user_id->nonce = 0; + krb5_free_principal(context, user_id->user); + user_id->user = NULL; + krb5_free_data_contents(context, &user_id->subject_cert); + user_id->subject_cert.length = 0; + user_id->subject_cert.data = NULL; + user_id->options = 0; +} + +void KRB5_CALLCONV +krb5_free_pa_s4u_x509_user(krb5_context context, krb5_pa_s4u_x509_user *req) +{ + if (req == NULL) + return; + krb5_free_s4u_userid_contents(context, &req->user_id); + krb5_free_checksum_contents(context, &req->cksum); + free(req); +} + +void KRB5_CALLCONV krb5_free_pa_server_referral_data(krb5_context context, krb5_pa_server_referral_data *ref) { diff --git a/src/lib/krb5/krb/preauth2.c b/src/lib/krb5/krb/preauth2.c index e6f4215d5..996cbfd36 100644 --- a/src/lib/krb5/krb/preauth2.c +++ b/src/lib/krb5/krb/preauth2.c @@ -1706,6 +1706,59 @@ krb5_error_code pa_sam_2(krb5_context context, return(0); } +static krb5_error_code pa_s4u_x509_user( + krb5_context context, + krb5_kdc_req *request, + krb5_pa_data *in_padata, + krb5_pa_data **out_padata, + krb5_data *salt, + krb5_data *s2kparams, + krb5_enctype *etype, + krb5_keyblock *as_key, + krb5_prompter_fct prompter, + void *prompter_data, + krb5_gic_get_as_key_fct gak_fct, + void *gak_data) +{ + krb5_s4u_userid *userid = (krb5_s4u_userid *)gak_data; /* XXX private contract */ + krb5_pa_data *s4u_padata; + krb5_error_code code; + krb5_principal client; + + *out_padata = NULL; + + if (userid == NULL) + return EINVAL; + + code = krb5_copy_principal(context, request->client, &client); + if (code != 0) + return code; + + if (userid->user != NULL) + krb5_free_principal(context, userid->user); + userid->user = client; + + if (userid->subject_cert.length != 0) { + s4u_padata = malloc(sizeof(*s4u_padata)); + if (s4u_padata == NULL) + return ENOMEM; + + s4u_padata->magic = KV5M_PA_DATA; + s4u_padata->pa_type = KRB5_PADATA_S4U_X509_USER; + s4u_padata->contents = malloc(userid->subject_cert.length); + if (s4u_padata->contents == NULL) { + free(s4u_padata); + return ENOMEM; + } + memcpy(s4u_padata->contents, userid->subject_cert.data, userid->subject_cert.length); + s4u_padata->length = userid->subject_cert.length; + + *out_padata = s4u_padata; + } + + return 0; +} + /* FIXME - order significant? */ static const pa_types_t pa_types[] = { { @@ -1751,6 +1804,11 @@ static const pa_types_t pa_types[] = { PA_INFO, }, { + KRB5_PADATA_S4U_X509_USER, + pa_s4u_x509_user, + PA_INFO, + }, + { -1, NULL, 0, diff --git a/src/lib/krb5/krb/s4u_creds.c b/src/lib/krb5/krb/s4u_creds.c new file mode 100644 index 000000000..613bbef1f --- /dev/null +++ b/src/lib/krb5/krb/s4u_creds.c @@ -0,0 +1,829 @@ +/* -*- mode: c; indent-tabs-mode: nil -*- */ +/* + * lib/krb5/krb/s4u_creds.c + * + * Copyright (C) 2009 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 "int-proto.h" + +/* Convert ticket flags to necessary KDC options */ +#define FLAGS2OPTS(flags) (flags & KDC_TKT_COMMON_MASK) + +/* + * Implements S4U2Self, by which a service can request a ticket to + * itself on behalf of an arbitrary principal. + */ + +static krb5_error_code +krb5_get_as_key_noop( + krb5_context context, + krb5_principal client, + krb5_enctype etype, + krb5_prompter_fct prompter, + void *prompter_data, + krb5_data *salt, + krb5_data *params, + krb5_keyblock *as_key, + void *gak_data) +{ + /* force a hard error, we don't actually have the key */ + return KDC_ERR_PREAUTH_FAILED; +} + +static krb5_error_code +s4u_identify_user(krb5_context context, + krb5_creds *in_creds, + krb5_data *subject_cert, + krb5_principal *canon_user) +{ + krb5_error_code code; + krb5_preauthtype ptypes[1] = { KRB5_PADATA_S4U_X509_USER }; + krb5_creds creds; + int use_master = 0; + krb5_get_init_creds_opt *opts = NULL; + krb5_gic_opt_ext *opte = NULL; + krb5_principal_data client_data; + krb5_principal client; + krb5_s4u_userid userid; + + *canon_user = NULL; + + if (in_creds->client == NULL && subject_cert == NULL) { + return EINVAL; + } + + if (in_creds->client != NULL && + krb5_princ_type(context, in_creds->client) != + KRB5_NT_ENTERPRISE_PRINCIPAL) + /* we already know the realm of the user */ + return krb5_copy_principal(context, in_creds->client, canon_user); + + memset(&creds, 0, sizeof(creds)); + + memset(&userid, 0, sizeof(userid)); + if (subject_cert != NULL) + userid.subject_cert = *subject_cert; + + code = krb5_get_init_creds_opt_alloc(context, &opts); + if (code != 0) + goto cleanup; + krb5_get_init_creds_opt_set_tkt_life(opts, 15); + krb5_get_init_creds_opt_set_renew_life(opts, 0); + krb5_get_init_creds_opt_set_forwardable(opts, 0); + krb5_get_init_creds_opt_set_proxiable(opts, 0); + krb5_get_init_creds_opt_set_canonicalize(opts, 1); + krb5_get_init_creds_opt_set_preauth_list(opts, ptypes, 1); + code = krb5int_gic_opt_to_opte(context, opts, &opte, + 0, "s4u_identify_user"); + if (code != 0) + goto cleanup; + + if (in_creds->client != NULL) + client = in_creds->client; + else { + client_data.magic = KV5M_PRINCIPAL; + client_data.realm = in_creds->server->realm; + /* should this be NULL, empty or a fixed string? XXX */ + client_data.data = NULL; + client_data.length = 0; + client_data.type = KRB5_NT_ENTERPRISE_PRINCIPAL; + client = &client_data; + } + + code = krb5_get_init_creds(context, &creds, in_creds->client, + NULL, NULL, 0, NULL, opte, + krb5_get_as_key_noop, &userid, + &use_master, NULL); + if (code == 0 || + code == KDC_ERR_PREAUTH_REQUIRED || + code == KDC_ERR_PREAUTH_FAILED) { + *canon_user = userid.user; + userid.user = NULL; + code = 0; + } + +cleanup: + krb5_free_cred_contents(context, &creds); + if (opts != NULL) + krb5_get_init_creds_opt_free(context, opts); + if (userid.user != NULL) + krb5_free_principal(context, userid.user); + + return code; +} + +static krb5_error_code +make_pa_for_user_checksum(krb5_context context, + krb5_keyblock *key, + krb5_pa_for_user *req, + krb5_checksum *cksum) +{ + krb5_error_code code; + int i; + krb5_int32 name_type; + char *p; + krb5_data data; + krb5_cksumtype cksumtype; + + data.length = 4; + for (i = 0; i < krb5_princ_size(context, req->user); i++) { + data.length += krb5_princ_component(context, req->user, i)->length; + } + data.length += krb5_princ_realm(context, req->user)->length; + data.length += req->auth_package.length; + + p = data.data = malloc(data.length); + if (data.data == NULL) + return ENOMEM; + + name_type = krb5_princ_type(context, req->user); + p[0] = (name_type >> 0 ) & 0xFF; + p[1] = (name_type >> 8 ) & 0xFF; + p[2] = (name_type >> 16) & 0xFF; + p[3] = (name_type >> 24) & 0xFF; + p += 4; + + for (i = 0; i < krb5_princ_size(context, req->user); i++) { + memcpy(p, krb5_princ_component(context, req->user, i)->data, + krb5_princ_component(context, req->user, i)->length); + p += krb5_princ_component(context, req->user, i)->length; + } + + memcpy(p, krb5_princ_realm(context, req->user)->data, + krb5_princ_realm(context, req->user)->length); + p += krb5_princ_realm(context, req->user)->length; + + memcpy(p, req->auth_package.data, req->auth_package.length); + + code = krb5int_c_mandatory_cksumtype(context, key->enctype, &cksumtype); + if (code != 0) { + free(data.data); + return code; + } + + code = krb5_c_make_checksum(context, cksumtype, key, + KRB5_KEYUSAGE_APP_DATA_CKSUM, &data, + cksum); + + free(data.data); + + return code; +} + +static krb5_error_code +build_pa_for_user(krb5_context context, + krb5_creds *tgt, + krb5_s4u_userid *userid, + krb5_pa_data **out_padata) +{ + krb5_error_code code; + krb5_pa_data *padata; + krb5_pa_for_user for_user; + krb5_data *for_user_data = NULL; + char package[] = "Kerberos"; + + if (userid->user == NULL) { + code = EINVAL; + goto cleanup; + } + + memset(&for_user, 0, sizeof(for_user)); + for_user.user = userid->user; + for_user.auth_package.data = package; + for_user.auth_package.length = sizeof(package) - 1; + + code = make_pa_for_user_checksum(context, &tgt->keyblock, + &for_user, &for_user.cksum); + if (code != 0) + goto cleanup; + + code = encode_krb5_pa_for_user(&for_user, &for_user_data); + if (code != 0) + goto cleanup; + + padata = malloc(sizeof(*padata)); + if (padata == NULL) { + code = ENOMEM; + goto cleanup; + } + + padata->magic = KV5M_PA_DATA; + padata->pa_type = KRB5_PADATA_FOR_USER; + padata->length = for_user_data->length; + padata->contents = (krb5_octet *)for_user_data->data; + + free(for_user_data); + for_user_data = NULL; + + *out_padata = padata; + +cleanup: + if (for_user.cksum.contents != NULL) + krb5_free_checksum_contents(context, &for_user.cksum); + krb5_free_data(context, for_user_data); + + return code; +} + +/* + * This function is invoked by krb5int_send_tgs() just before + * the request is encoded; it gives us access to the nonce and + * subkey without requiring them to be generated by the caller. + */ +static krb5_error_code +build_pa_s4u_x509_user(krb5_context context, + krb5_keyblock *subkey, + krb5_kdc_req *tgsreq, + void *gcvt_data) +{ + krb5_error_code code; + krb5_pa_s4u_x509_user *s4u_user = (krb5_pa_s4u_x509_user *)gcvt_data; + krb5_data *data = NULL; + krb5_pa_data **padata; + krb5_cksumtype cksumtype; + int i; + + assert(s4u_user->cksum.contents == NULL); + + s4u_user->user_id.nonce = tgsreq->nonce; + + code = encode_krb5_s4u_userid(&s4u_user->user_id, &data); + if (code != 0) + goto cleanup; + + /* [MS-SFU] 2.2.2: unusual to say the least, but enc_padata secures it */ + if (subkey->enctype == ENCTYPE_ARCFOUR_HMAC || + subkey->enctype == ENCTYPE_ARCFOUR_HMAC_EXP) { + cksumtype = CKSUMTYPE_RSA_MD4; + } else { + code = krb5int_c_mandatory_cksumtype(context, subkey->enctype, + &cksumtype); + } + if (code != 0) + goto cleanup; + + code = krb5_c_make_checksum(context, cksumtype, subkey, + KRB5_KEYUSAGE_PA_S4U_X509_USER_REQUEST, data, + &s4u_user->cksum); + if (code != 0) + goto cleanup; + + krb5_free_data(context, data); + data = NULL; + + code = encode_krb5_pa_s4u_x509_user(s4u_user, &data); + if (code != 0) + goto cleanup; + + assert(tgsreq->padata != NULL); + + for (i = 0; tgsreq->padata[i] != NULL; i++) + ; + + padata = realloc(tgsreq->padata, + (i + 2) * sizeof(krb5_pa_data *)); + if (padata == NULL) { + code = ENOMEM; + goto cleanup; + } + tgsreq->padata = padata; + + padata[i] = malloc(sizeof(krb5_pa_data)); + if (padata[i] == NULL) { + code = ENOMEM; + goto cleanup; + } + padata[i]->magic = KV5M_PA_DATA; + padata[i]->pa_type = KRB5_PADATA_S4U_X509_USER; + padata[i]->length = data->length; + padata[i]->contents = (krb5_octet *)data->data; + + padata[i + 1] = NULL; + + free(data); + data = NULL; + +cleanup: + if (code != 0 && s4u_user->cksum.contents != NULL) { + krb5_free_checksum_contents(context, &s4u_user->cksum); + s4u_user->cksum.contents = NULL; + } + krb5_free_data(context, data); + + return code; +} + +static krb5_error_code +verify_s4u2self_reply(krb5_context context, + krb5_keyblock *subkey, + krb5_pa_s4u_x509_user *req_s4u_user, + krb5_pa_data **rep_padata, + krb5_pa_data **enc_padata) +{ + krb5_error_code code; + krb5_pa_data *rep_s4u_padata, *enc_s4u_padata; + krb5_pa_s4u_x509_user *rep_s4u_user = NULL; + krb5_data data, *datap = NULL; + krb5_keyusage usage; + krb5_boolean valid; + krb5_boolean not_newer; + + assert(req_s4u_user != NULL); + + switch (subkey->enctype) { + case ENCTYPE_DES_CBC_CRC: + case ENCTYPE_DES_CBC_MD4: + case ENCTYPE_DES_CBC_MD5: + case ENCTYPE_DES3_CBC_SHA1: + case ENCTYPE_DES3_CBC_RAW: + case ENCTYPE_ARCFOUR_HMAC: + case ENCTYPE_ARCFOUR_HMAC_EXP : + not_newer = TRUE; + break; + default: + not_newer = FALSE; + break; + } + + enc_s4u_padata = krb5int_find_pa_data(context, + enc_padata, + KRB5_PADATA_S4U_X509_USER); + + /* XXX this will break newer enctypes with a MIT 1.7 KDC */ + rep_s4u_padata = krb5int_find_pa_data(context, + rep_padata, + KRB5_PADATA_S4U_X509_USER); + if (rep_s4u_padata == NULL) { + if (not_newer == FALSE || enc_s4u_padata != NULL) + return KRB5_KDCREP_MODIFIED; + else + return 0; + } + + data.length = rep_s4u_padata->length; + data.data = (char *)rep_s4u_padata->contents; + + code = decode_krb5_pa_s4u_x509_user(&data, &rep_s4u_user); + if (code != 0) + goto cleanup; + + if (rep_s4u_user->user_id.nonce != req_s4u_user->user_id.nonce) { + code = KRB5_KDCREP_MODIFIED; + goto cleanup; + } + + code = encode_krb5_s4u_userid(&rep_s4u_user->user_id, &datap); + if (code != 0) + goto cleanup; + + if (rep_s4u_user->user_id.options & KRB5_S4U_OPTS_USE_REPLY_KEY_USAGE) + usage = KRB5_KEYUSAGE_PA_S4U_X509_USER_REPLY; + else + usage = KRB5_KEYUSAGE_PA_S4U_X509_USER_REQUEST; + + code = krb5_c_verify_checksum(context, subkey, usage, datap, + &rep_s4u_user->cksum, &valid); + if (code != 0) + goto cleanup; + if (valid == FALSE) { + code = KRB5_KDCREP_MODIFIED; + goto cleanup; + } + + /* + * KDCs that support KRB5_S4U_OPTS_USE_REPLY_KEY_USAGE also return + * S4U enc_padata for older (pre-AES) encryption types only. + */ + if (not_newer) { + if (enc_s4u_padata == NULL) { + if (rep_s4u_user->user_id.options & + KRB5_S4U_OPTS_USE_REPLY_KEY_USAGE) { + code = KRB5_KDCREP_MODIFIED; + goto cleanup; + } + } else { + if (enc_s4u_padata->length != + req_s4u_user->cksum.length + rep_s4u_user->cksum.length) { + code = KRB5_KDCREP_MODIFIED; + goto cleanup; + } + if (memcmp(enc_s4u_padata->contents, + req_s4u_user->cksum.contents, + req_s4u_user->cksum.length) || + memcmp(&enc_s4u_padata->contents[req_s4u_user->cksum.length], + rep_s4u_user->cksum.contents, + rep_s4u_user->cksum.length)) { + code = KRB5_KDCREP_MODIFIED; + goto cleanup; + } + } + } else if (!krb5_c_is_keyed_cksum(rep_s4u_user->cksum.checksum_type)) { + code = KRB5KRB_AP_ERR_INAPP_CKSUM; + goto cleanup; + } + +cleanup: + krb5_free_pa_s4u_x509_user(context, rep_s4u_user); + krb5_free_data(context, datap); + + return code; +} + +static krb5_error_code +krb5_get_self_cred_from_kdc(krb5_context context, + krb5_flags options, + krb5_ccache ccache, + krb5_creds *in_creds, + krb5_data *subject_cert, + krb5_data *user_realm, + krb5_creds **out_creds) +{ + krb5_error_code code; + krb5_principal tgs = NULL; + krb5_creds tgtq, s4u_creds, *tgt = NULL, *tgtptr; + krb5_creds *referral_tgts[KRB5_REFERRAL_MAXHOPS]; + krb5_pa_s4u_x509_user s4u_user; + int referral_count = 0, i; + krb5_flags kdcopt; + + memset(&tgtq, 0, sizeof(tgtq)); + memset(&s4u_creds, 0, sizeof(s4u_creds)); + memset(referral_tgts, 0, sizeof(referral_tgts)); + *out_creds = NULL; + + memset(&s4u_user, 0, sizeof(s4u_user)); + + if (in_creds->client != NULL && + krb5_princ_size(context, in_creds->client)) { + if (krb5_princ_type(context, in_creds->client) == + KRB5_NT_ENTERPRISE_PRINCIPAL) + { + code = krb5_build_principal_ext(context, + &s4u_user.user_id.user, + user_realm->length, + user_realm->data, + in_creds->client->data[0].length, + in_creds->client->data[0].data, + 0); + if (code != 0) + goto cleanup; + s4u_user.user_id.user->type = KRB5_NT_ENTERPRISE_PRINCIPAL; + } else { + code = krb5_copy_principal(context, + in_creds->client, + &s4u_user.user_id.user); + if (code != 0) + goto cleanup; + } + } else { + code = krb5_build_principal_ext(context, &s4u_user.user_id.user, + user_realm->length, + user_realm->data); + if (code != 0) + goto cleanup; + s4u_user.user_id.user->type = KRB5_NT_ENTERPRISE_PRINCIPAL; + } + if (subject_cert != NULL) + s4u_user.user_id.subject_cert = *subject_cert; + s4u_user.user_id.options = KRB5_S4U_OPTS_USE_REPLY_KEY_USAGE; + + /* First, acquire a TGT to the user's realm. */ + code = krb5_tgtname(context, user_realm, + krb5_princ_realm(context, in_creds->server), &tgs); + if (code != 0) + goto cleanup; + + tgtq.client = in_creds->server; + tgtq.server = tgs; + + code = krb5_get_credentials(context, options, ccache, &tgtq, &tgt); + if (code != 0) + goto cleanup; + + tgtptr = tgt; + + code = krb5int_copy_creds_contents(context, in_creds, &s4u_creds); + if (code != 0) + goto cleanup; + + if (s4u_creds.client != NULL) { + krb5_free_principal(context, s4u_creds.client); + s4u_creds.client = NULL; + } + + code = krb5_copy_principal(context, in_creds->server, &s4u_creds.client); + if (code != 0) + goto cleanup; + + /* Then, walk back the referral path to S4U2Self for user */ + kdcopt = 0; + if (options & KRB5_GC_CANONICALIZE) + kdcopt |= KDC_OPT_CANONICALIZE; + if (options & KRB5_GC_FORWARDABLE) + kdcopt |= KDC_OPT_FORWARDABLE; + if (options & KRB5_GC_NO_TRANSIT_CHECK) + kdcopt |= KDC_OPT_DISABLE_TRANSITED_CHECK; + + for (referral_count = 0; + referral_count < KRB5_REFERRAL_MAXHOPS; + referral_count++) + { + krb5_pa_data **in_padata = NULL; + krb5_pa_data **out_padata = NULL; + krb5_pa_data **enc_padata = NULL; + krb5_keyblock *subkey = NULL; + + if (s4u_user.user_id.user != NULL && + krb5_princ_size(context, s4u_user.user_id.user)) { + in_padata = calloc(2, sizeof(krb5_pa_data *)); + if (in_padata == NULL) { + code = ENOMEM; + goto cleanup; + } + code = build_pa_for_user(context, + tgtptr, + &s4u_user.user_id, &in_padata[0]); + if (code != 0) { + krb5_free_pa_data(context, in_padata); + goto cleanup; + } + } + + /* Rewrite server realm to match TGS realm */ + krb5_free_data_contents(context, &s4u_creds.server->realm); + + code = krb5int_copy_data_contents(context, + &tgtptr->server->data[1], + &s4u_creds.server->realm); + if (code != 0) + goto cleanup; + + code = krb5_get_cred_via_tkt_ext(context, tgtptr, + KDC_OPT_CANONICALIZE | + FLAGS2OPTS(tgtptr->ticket_flags) | + kdcopt, + tgtptr->addresses, + in_padata, &s4u_creds, + build_pa_s4u_x509_user, &s4u_user, + &out_padata, &enc_padata, + out_creds, &subkey); + if (code != 0) { + krb5_free_checksum_contents(context, &s4u_user.cksum); + krb5_free_pa_data(context, in_padata); + goto cleanup; + } + + code = verify_s4u2self_reply(context, subkey, &s4u_user, + out_padata, enc_padata); + + krb5_free_checksum_contents(context, &s4u_user.cksum); + krb5_free_pa_data(context, in_padata); + krb5_free_pa_data(context, out_padata); + krb5_free_pa_data(context, enc_padata); + krb5_free_keyblock(context, subkey); + + if (code != 0) + goto cleanup; + + if (krb5_principal_compare(context, + in_creds->server, + (*out_creds)->server)) { + code = 0; + goto cleanup; + } else if (IS_TGS_PRINC(context, (*out_creds)->server)) { + krb5_data *r1 = &tgtptr->server->data[1]; + krb5_data *r2 = &(*out_creds)->server->data[1]; + + if (data_eq(*r1, *r2)) { + krb5_free_creds(context, *out_creds); + *out_creds = NULL; + code = KRB5_ERR_HOST_REALM_UNKNOWN; + break; + } + for (i = 0; i < referral_count; i++) { + if (krb5_principal_compare(context, + (*out_creds)->server, + referral_tgts[i]->server)) { + code = KRB5_KDC_UNREACH; + goto cleanup; + } + } + + tgtptr = *out_creds; + referral_tgts[referral_count] = *out_creds; + *out_creds = NULL; + } else { + krb5_free_creds(context, *out_creds); + *out_creds = NULL; + code = KRB5KRB_AP_WRONG_PRINC; /* XXX */ + break; + } + } + +cleanup: + for (i = 0; i < KRB5_REFERRAL_MAXHOPS; i++) { + if (referral_tgts[i] != NULL) + krb5_free_creds(context, referral_tgts[i]); + } + krb5_free_principal(context, tgs); + krb5_free_creds(context, tgt); + krb5_free_cred_contents(context, &s4u_creds); + krb5_free_principal(context, s4u_user.user_id.user); + krb5_free_checksum_contents(context, &s4u_user.cksum); + + return code; +} + +krb5_error_code KRB5_CALLCONV +krb5_get_credentials_for_user(krb5_context context, krb5_flags options, + krb5_ccache ccache, krb5_creds *in_creds, + krb5_data *subject_cert, + krb5_creds **out_creds) +{ + krb5_error_code code; + krb5_principal realm = NULL; + + *out_creds = NULL; + + if (options & KRB5_GC_CONSTRAINED_DELEGATION) { + code = EINVAL; + goto cleanup; + } + + if (in_creds->client != NULL) { + /* Uncanonicalised check */ + code = krb5_get_credentials(context, options | KRB5_GC_CACHED, + ccache, in_creds, out_creds); + if (code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE) + goto cleanup; + + if ((options & KRB5_GC_CACHED) && !(options & KRB5_GC_CANONICALIZE)) + goto cleanup; + } + + code = s4u_identify_user(context, in_creds, subject_cert, &realm); + if (code != 0) + goto cleanup; + + code = krb5_get_credentials(context, options | KRB5_GC_CACHED, + ccache, in_creds, out_creds); + if ((code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE) + || options & KRB5_GC_CACHED) + goto cleanup; + + code = krb5_get_self_cred_from_kdc(context, options, ccache, + in_creds, subject_cert, + krb5_princ_realm(context, realm), + out_creds); + if (code != 0) + goto cleanup; + + assert(*out_creds != NULL); + + if ((options & KRB5_GC_NO_STORE) == 0) { + code = krb5_cc_store_cred(context, ccache, *out_creds); + if (code != 0) + goto cleanup; + } + +cleanup: + if (code != 0 && *out_creds != NULL) { + krb5_free_creds(context, *out_creds); + *out_creds = NULL; + } + + krb5_free_principal(context, realm); + + return code; +} + +/* + * Exported API for constrained delegation (S4U2Proxy). + * + * This is preferable to using krb5_get_credentials directly because + * it can perform some additional checks. + */ +krb5_error_code KRB5_CALLCONV +krb5_get_credentials_for_proxy(krb5_context context, + krb5_flags options, + krb5_ccache ccache, + krb5_creds *in_creds, + krb5_ticket *evidence_tkt, + krb5_creds **out_creds) +{ + krb5_error_code code; + krb5_creds mcreds; + krb5_creds *ncreds = NULL; + krb5_flags fields; + krb5_data *evidence_tkt_data = NULL; + krb5_creds s4u_creds; + + *out_creds = NULL; + + if (in_creds == NULL || in_creds->client == NULL || + evidence_tkt == NULL || evidence_tkt->enc_part2 == NULL) { + code = EINVAL; + goto cleanup; + } + + /* + * Caller should have set in_creds->client to match evidence + * ticket client + */ + if (!krb5_principal_compare(context, evidence_tkt->enc_part2->client, + in_creds->client)) { + code = EINVAL; + goto cleanup; + } + + if ((evidence_tkt->enc_part2->flags & TKT_FLG_FORWARDABLE) == 0) { + code = KRB5_TKT_NOT_FORWARDABLE; + goto cleanup; + } + + code = krb5_get_credentials_core(context, options, in_creds, + &mcreds, &fields); + if (code != 0) + goto cleanup; + + ncreds = calloc(1, sizeof(*ncreds)); + if (ncreds == NULL) { + code = ENOMEM; + goto cleanup; + } + ncreds->magic = KV5M_CRED; + + code = krb5_cc_retrieve_cred(context, ccache, fields, &mcreds, ncreds); + if (code != 0) { + free(ncreds); + ncreds = in_creds; + } else { + *out_creds = ncreds; + } + + if ((code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE) + || options & KRB5_GC_CACHED) + goto cleanup; + + code = encode_krb5_ticket(evidence_tkt, &evidence_tkt_data); + if (code != 0) + goto cleanup; + + s4u_creds = *in_creds; + s4u_creds.client = evidence_tkt->server; + s4u_creds.second_ticket = *evidence_tkt_data; + + code = krb5_get_credentials(context, + options | KRB5_GC_CONSTRAINED_DELEGATION, + ccache, + &s4u_creds, + out_creds); + if (code != 0) + goto cleanup; + + /* + * Check client name because we couldn't compare that inside + * krb5_get_credentials() (enc_part2 is unavailable in clear) + */ + if (!krb5_principal_compare(context, + evidence_tkt->enc_part2->client, + (*out_creds)->client)) { + code = KRB5_KDCREP_MODIFIED; + goto cleanup; + } + +cleanup: + if (*out_creds != NULL && code != 0) { + krb5_free_creds(context, *out_creds); + *out_creds = NULL; + } + if (evidence_tkt_data != NULL) + krb5_free_data(context, evidence_tkt_data); + + return code; +} diff --git a/src/lib/krb5/krb/send_tgs.c b/src/lib/krb5/krb/send_tgs.c index 97cd02bf7..eee47ed57 100644 --- a/src/lib/krb5/krb/send_tgs.c +++ b/src/lib/krb5/krb/send_tgs.c @@ -77,7 +77,7 @@ tgs_construct_tgsreq(krb5_context context, krb5_data *in_data, if (retval) goto cleanup; } - + /* Generate checksum */ if ((retval = krb5_c_make_checksum(context, cksumtype, &in_cred->keyblock, @@ -142,6 +142,9 @@ cleanup: } /* * Note that this function fills in part of rep even on failure. + * + * The pacb_fct callback allows the caller access to the nonce + * and request subkey, for binding preauthentication data */ krb5_error_code krb5int_send_tgs(krb5_context context, krb5_flags kdcoptions, @@ -149,7 +152,13 @@ krb5int_send_tgs(krb5_context context, krb5_flags kdcoptions, krb5_const_principal sname, krb5_address *const *addrs, krb5_authdata *const *authorization_data, krb5_pa_data *const *padata, const krb5_data *second_ticket, - krb5_creds *in_cred, krb5_response *rep, krb5_keyblock **subkey) + krb5_creds *in_cred, + krb5_error_code (*pacb_fct)(krb5_context, + krb5_keyblock *, + krb5_kdc_req *, + void *), + void *pacb_data, + krb5_response *rep, krb5_keyblock **subkey) { krb5_error_code retval; krb5_kdc_req tgsreq; @@ -157,13 +166,14 @@ krb5int_send_tgs(krb5_context context, krb5_flags kdcoptions, krb5_ticket *sec_ticket = 0; krb5_ticket *sec_ticket_arr[2]; krb5_timestamp time_now; - krb5_pa_data **combined_padata; + krb5_pa_data **combined_padata = NULL; krb5_pa_data ap_req_padata; int tcp_only = 0, use_master; krb5_keyblock *local_subkey = NULL; assert (subkey != NULL); *subkey = NULL; + /* * in_creds MUST be a valid credential NOT just a partially filled in * place holder for us to get credentials for the caller. @@ -215,8 +225,8 @@ krb5int_send_tgs(krb5_context context, krb5_flags kdcoptions, /* Get the encryption types list */ if (ktypes) { - /* Check passed ktypes and make sure they're valid. */ - for (tgsreq.nktypes = 0; ktypes[tgsreq.nktypes]; tgsreq.nktypes++) { + /* Check passed ktypes and make sure they're valid. */ + for (tgsreq.nktypes = 0; ktypes[tgsreq.nktypes]; tgsreq.nktypes++) { if (!krb5_c_valid_enctype(ktypes[tgsreq.nktypes])) return KRB5_PROG_ETYPE_NOSUPP; } @@ -236,6 +246,8 @@ krb5int_send_tgs(krb5_context context, krb5_flags kdcoptions, } else tgsreq.second_ticket = 0; + ap_req_padata.contents = NULL; + /* encode the body; then checksum it */ if ((retval = encode_krb5_kdc_req_body(&tgsreq, &scratch))) goto send_tgs_error_2; @@ -250,47 +262,74 @@ krb5int_send_tgs(krb5_context context, krb5_flags kdcoptions, } krb5_free_data(context, scratch); - ap_req_padata.pa_type = KRB5_PADATA_AP_REQ; - ap_req_padata.length = scratch2.length; - ap_req_padata.contents = (krb5_octet *)scratch2.data; - - /* combine in any other supplied padata */ + tgsreq.padata = (krb5_pa_data **)calloc(2, sizeof(krb5_pa_data *)); + if (tgsreq.padata == NULL) { + free(scratch2.data); + goto send_tgs_error_2; + } + tgsreq.padata[0] = (krb5_pa_data *)malloc(sizeof(krb5_pa_data)); + if (tgsreq.padata[0] == NULL) { + free(scratch2.data); + goto send_tgs_error_2; + } + tgsreq.padata[0]->pa_type = KRB5_PADATA_AP_REQ; + tgsreq.padata[0]->length = scratch2.length; + tgsreq.padata[0]->contents = (krb5_octet *)scratch2.data; + tgsreq.padata[1] = NULL; + + /* combine in any other supplied padata, unfortunately now it is + * necessary to copy it as the callback function might modify the + * padata, and having a separate path for the non-callback case, + * or attempting to determine which elements were changed by the + * callback, would have complicated the code significantly. + */ if (padata) { - krb5_pa_data * const * counter; - register unsigned int i = 0; - for (counter = padata; *counter; counter++, i++); - combined_padata = malloc((i+2) * sizeof(*combined_padata)); - if (!combined_padata) { - free(ap_req_padata.contents); - retval = ENOMEM; - goto send_tgs_error_2; - } - combined_padata[0] = &ap_req_padata; - for (i = 1, counter = padata; *counter; counter++, i++) - combined_padata[i] = (krb5_pa_data *) *counter; - combined_padata[i] = 0; - } else { - combined_padata = (krb5_pa_data **)malloc(2*sizeof(*combined_padata)); - if (!combined_padata) { - free(ap_req_padata.contents); - retval = ENOMEM; + krb5_pa_data **tmp; + int i; + + for (i = 0; padata[i]; i++) + ; + + tmp = (krb5_pa_data **)realloc(tgsreq.padata, + (i + 2) * sizeof(*combined_padata)); + if (tmp == NULL) goto send_tgs_error_2; + + tgsreq.padata = tmp; + + for (i = 0; padata[i]; i++) { + krb5_pa_data *pa; + + pa = tgsreq.padata[1 + i] = (krb5_pa_data *)malloc(sizeof(krb5_pa_data)); + if (tgsreq.padata == NULL) { + retval = ENOMEM; + goto send_tgs_error_2; + } + + pa->pa_type = padata[i]->pa_type; + pa->length = padata[i]->length; + pa->contents = (krb5_octet *)malloc(padata[i]->length); + if (pa->contents == NULL) { + retval = ENOMEM; + goto send_tgs_error_2; + } + memcpy(pa->contents, padata[i]->contents, padata[i]->length); } - combined_padata[0] = &ap_req_padata; - combined_padata[1] = 0; + tgsreq.padata[1 + i] = NULL; } - tgsreq.padata = combined_padata; + if (pacb_fct != NULL) { + if ((retval = (*pacb_fct)(context, local_subkey, &tgsreq, pacb_data))) + goto send_tgs_error_2; + } /* the TGS_REQ is assembled in tgsreq, so encode it */ - if ((retval = encode_krb5_tgs_req(&tgsreq, &scratch))) { - free(ap_req_padata.contents); - free(combined_padata); + if ((retval = encode_krb5_tgs_req(&tgsreq, &scratch))) goto send_tgs_error_2; - } - free(ap_req_padata.contents); - free(combined_padata); /* now send request & get response from KDC */ + krb5_free_pa_data(context, tgsreq.padata); + tgsreq.padata = NULL; + send_again: use_master = 0; retval = krb5_sendto_kdc(context, scratch, @@ -325,6 +364,8 @@ send_again: krb5_free_data(context, scratch); send_tgs_error_2:; + if (tgsreq.padata) + krb5_free_pa_data(context, tgsreq.padata); if (sec_ticket) krb5_free_ticket(context, sec_ticket); diff --git a/src/lib/krb5/krb/srv_dec_tkt.c b/src/lib/krb5/krb/srv_dec_tkt.c index b5cf260f2..0934e27e1 100644 --- a/src/lib/krb5/krb/srv_dec_tkt.c +++ b/src/lib/krb5/krb/srv_dec_tkt.c @@ -70,27 +70,70 @@ krb5int_server_decrypt_ticket_keyblock(krb5_context context, } -krb5_error_code KRB5_CALLCONV +krb5_error_code KRB5_CALLCONV krb5_server_decrypt_ticket_keytab(krb5_context context, - const krb5_keytab kt, + const krb5_keytab keytab, krb5_ticket *ticket) { - krb5_error_code retval; - krb5_enctype enctype; - krb5_keytab_entry ktent; + krb5_error_code retval; + krb5_keytab_entry ktent; + + retval = KRB5_KT_NOTFOUND; + + if (keytab->ops->start_seq_get == NULL) { + retval = krb5_kt_get_entry(context, keytab, + ticket->server, + ticket->enc_part.kvno, + ticket->enc_part.enctype, &ktent); + if (retval == 0) { + retval = krb5int_server_decrypt_ticket_keyblock(context, &ktent.key, 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; - enctype = ticket->enc_part.enctype; + while ((code = krb5_kt_next_entry(context, keytab, + &ktent, &cursor)) == 0) { + if (ktent.key.enctype != ticket->enc_part.enctype) + continue; - if ((retval = krb5_kt_get_entry(context, kt, ticket->server, - ticket->enc_part.kvno, - enctype, &ktent))) - return retval; + retval = krb5int_server_decrypt_ticket_keyblock(context, &ktent.key, ticket); + if (retval == 0) { + krb5_principal tmp; - retval = krb5int_server_decrypt_ticket_keyblock(context, - &ktent.key, ticket); - /* Upon error, Free keytab entry first, then return */ + retval = krb5_copy_principal(context, ktent.principal, &tmp); + if (retval == 0) { + krb5_free_principal(context, ticket->server); + 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; + } + +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; + } - (void) krb5_kt_free_entry(context, &ktent); return retval; } #endif /* LEAN_CLIENT */ diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports index bd50fddb5..b809e83cf 100644 --- a/src/lib/krb5/libkrb5.exports +++ b/src/lib/krb5/libkrb5.exports @@ -20,11 +20,12 @@ decode_krb5_error decode_krb5_etype_info decode_krb5_etype_info2 decode_krb5_fast_req -decode_krb5_pa_fx_fast_request decode_krb5_kdc_req_body decode_krb5_pa_enc_ts decode_krb5_pa_for_user +decode_krb5_pa_fx_fast_request decode_krb5_pa_pac_req +decode_krb5_pa_s4u_x509_user decode_krb5_padata_sequence decode_krb5_predicted_sam_response decode_krb5_priv @@ -60,10 +61,11 @@ encode_krb5_error encode_krb5_etype_info encode_krb5_etype_info2 encode_krb5_fast_response -encode_krb5_pa_fx_fast_reply encode_krb5_kdc_req_body encode_krb5_pa_enc_ts encode_krb5_pa_for_user +encode_krb5_pa_fx_fast_reply +encode_krb5_pa_s4u_x509_user encode_krb5_pa_server_referral_data encode_krb5_pa_svr_referral_data encode_krb5_padata_sequence @@ -71,6 +73,7 @@ encode_krb5_predicted_sam_response encode_krb5_priv encode_krb5_pwd_data encode_krb5_pwd_sequence +encode_krb5_s4u_userid encode_krb5_safe encode_krb5_sam_challenge encode_krb5_sam_key @@ -134,9 +137,9 @@ krb5_auth_con_setsendsubkey krb5_auth_con_setuseruserkey krb5_auth_to_rep krb5_build_principal +krb5_build_principal_alloc_va krb5_build_principal_ext krb5_build_principal_va -krb5_build_principal_alloc_va krb5_cc_close krb5_cc_copy_creds krb5_cc_default @@ -243,8 +246,9 @@ krb5_free_ktypes krb5_free_last_req krb5_free_pa_data krb5_free_pa_enc_ts -krb5_free_pa_pac_req krb5_free_pa_for_user +krb5_free_pa_pac_req +krb5_free_pa_s4u_x509_user krb5_free_pa_server_referral_data krb5_free_pa_svr_referral_data krb5_free_passwd_phrase_element @@ -284,6 +288,8 @@ krb5_get_cred_from_kdc_renew krb5_get_cred_from_kdc_validate krb5_get_cred_via_tkt krb5_get_credentials +krb5_get_credentials_for_proxy +krb5_get_credentials_for_user krb5_get_credentials_renew krb5_get_credentials_validate krb5_get_default_config_files @@ -380,7 +386,6 @@ krb5_os_free_context krb5_os_hostaddr krb5_os_init_context krb5_os_localaddr -krb5int_get_domain_realm_mapping krb5_overridekeyname krb5_pac_add_buffer krb5_pac_free @@ -529,6 +534,7 @@ krb5int_find_authdata krb5int_find_pa_data krb5int_foreach_localaddr krb5int_free_addrlist +krb5int_get_domain_realm_mapping krb5int_init_context_kdc krb5int_initialize_library krb5int_pac_sign diff --git a/src/lib/krb5/os/sendto_kdc.c b/src/lib/krb5/os/sendto_kdc.c index dcf08d996..3e5f9e234 100644 --- a/src/lib/krb5/os/sendto_kdc.c +++ b/src/lib/krb5/os/sendto_kdc.c @@ -57,7 +57,7 @@ #define DEFAULT_UDP_PREF_LIMIT 1465 #define HARD_UDP_LIMIT 32700 /* could probably do 64K-epsilon ? */ -#undef DEBUG +#define DEBUG 1 #ifdef DEBUG int krb5int_debug_sendto_kdc = 0; diff --git a/src/slave/kproplog.c b/src/slave/kproplog.c index 43a7738d4..6fb2e2288 100644 --- a/src/slave/kproplog.c +++ b/src/slave/kproplog.c @@ -40,23 +40,31 @@ static void print_flags(unsigned int flags) { unsigned int i; - static char *prflags[] = { - "DISALLOW_POSTDATED", /* 0x00000001 */ - "DISALLOW_FORWARDABLE", /* 0x00000002 */ - "DISALLOW_TGT_BASED", /* 0x00000004 */ - "DISALLOW_RENEWABLE", /* 0x00000008 */ - "DISALLOW_PROXIABLE", /* 0x00000010 */ - "DISALLOW_DUP_SKEY", /* 0x00000020 */ - "DISALLOW_ALL_TIX", /* 0x00000040 */ - "REQUIRES_PRE_AUTH", /* 0x00000080 */ - "REQUIRES_HW_AUTH", /* 0x00000100 */ - "REQUIRES_PWCHANGE", /* 0x00000200 */ - "UNKNOWN_0x00000400", /* 0x00000400 */ - "UNKNOWN_0x00000800", /* 0x00000800 */ - "DISALLOW_SVR", /* 0x00001000 */ - "PWCHANGE_SERVICE", /* 0x00002000 */ - "SUPPORT_DESMD5", /* 0x00004000 */ - "NEW_PRINC", /* 0x00008000 */ + static char *prflags[] = { + "DISALLOW_POSTDATED", /* 0x00000001 */ + "DISALLOW_FORWARDABLE", /* 0x00000002 */ + "DISALLOW_TGT_BASED", /* 0x00000004 */ + "DISALLOW_RENEWABLE", /* 0x00000008 */ + "DISALLOW_PROXIABLE", /* 0x00000010 */ + "DISALLOW_DUP_SKEY", /* 0x00000020 */ + "DISALLOW_ALL_TIX", /* 0x00000040 */ + "REQUIRES_PRE_AUTH", /* 0x00000080 */ + "REQUIRES_HW_AUTH", /* 0x00000100 */ + "REQUIRES_PWCHANGE", /* 0x00000200 */ + "UNKNOWN_0x00000400", /* 0x00000400 */ + "UNKNOWN_0x00000800", /* 0x00000800 */ + "DISALLOW_SVR", /* 0x00001000 */ + "PWCHANGE_SERVICE", /* 0x00002000 */ + "SUPPORT_DESMD5", /* 0x00004000 */ + "NEW_PRINC", /* 0x00008000 */ + "UNKNOWN_0x00010000", /* 0x00010000 */ + "UNKNOWN_0x00020000", /* 0x00020000 */ + "UNKNOWN_0x00040000", /* 0x00040000 */ + "UNKNOWN_0x00080000", /* 0x00080000 */ + "OK_AS_DELEGATE", /* 0x00100000 */ + "OK_TO_AUTH_AS_DELEGATE", /* 0x00200000 */ + "NO_AUTH_DATA_REQUIRED", /* 0x00400000 */ + }; for (i = 0; i < sizeof (prflags) / sizeof (char *); i++) { @@ -169,7 +177,7 @@ print_key(kdbe_key_t *k) for (i = 0; i < k->k_enctype.k_enctype_len; i++) { printf("\t\t\tenc type: 0x%x\n", - k->k_enctype.k_enctype_val[i]); + k->k_enctype.k_enctype_val[i]); } str = k->k_contents.k_contents_val; diff --git a/src/tests/asn.1/krb5_decode_leak.c b/src/tests/asn.1/krb5_decode_leak.c index 41045b5a8..be0a536e9 100644 --- a/src/tests/asn.1/krb5_decode_leak.c +++ b/src/tests/asn.1/krb5_decode_leak.c @@ -658,7 +658,18 @@ main(int argc, char **argv) krb5_free_enc_sam_response_enc_2); ktest_empty_enc_sam_response_enc_2(&sam_ch2); } + /****************************************************************/ + /* encode_krb5_pa_s4u_x509_user */ + { + krb5_pa_s4u_x509_user s4u, *tmp; + setup(s4u, "pa_s4u_x509_user", + ktest_make_sample_pa_s4u_x509_user); + leak_test(s4u, encode_krb5_pa_s4u_x509_user, + decode_krb5_pa_s4u_x509_user, + krb5_free_pa_s4u_x509_user); + ktest_empty_pa_s4u_x509_user(&s4u); + } krb5_free_context(test_context); return 0; } diff --git a/src/tests/asn.1/krb5_decode_test.c b/src/tests/asn.1/krb5_decode_test.c index 7136669ac..2d2000422 100644 --- a/src/tests/asn.1/krb5_decode_test.c +++ b/src/tests/asn.1/krb5_decode_test.c @@ -890,7 +890,13 @@ int main(argc, argv) ktest_empty_sam_response(&ref); } - + + { + setup(krb5_pa_s4u_x509_user,"krb5_pa_s4u_x509_user",ktest_make_sample_pa_s4u_x509_user); + decode_run("pa_s4u_x509_user","","30 68 A0 55 30 53 A0 06 02 04 00 CA 14 9A A1 1A 30 18 A0 03 02 01 01 A1 11 30 0F 1B 06 68 66 74 73 61 69 1B 05 65 78 74 72 61 A2 10 1B 0E 41 54 48 45 4E 41 2E 4D 49 54 2E 45 44 55 A3 12 04 10 70 61 5F 73 34 75 5F 78 35 30 39 5F 75 73 65 72 A4 07 03 05 00 80 00 00 00 A1 0F 30 0D A0 03 02 01 01 A1 06 04 04 31 32 33 34",decode_krb5_pa_s4u_x509_user,ktest_equal_pa_s4u_x509_user,krb5_free_pa_s4u_x509_user); + ktest_empty_pa_s4u_x509_user(&ref); + } + #ifdef ENABLE_LDAP /* ldap sequence_of_keys */ { diff --git a/src/tests/asn.1/krb5_encode_test.c b/src/tests/asn.1/krb5_encode_test.c index 2da5c1e7f..7ae32ec75 100644 --- a/src/tests/asn.1/krb5_encode_test.c +++ b/src/tests/asn.1/krb5_encode_test.c @@ -695,6 +695,18 @@ main(argc, argv) acc.encode_krb5_enc_sam_response_enc_2); ktest_empty_enc_sam_response_enc_2(&sam_ch2); } + /****************************************************************/ + /* encode_krb5_pa_s4u_x509_user */ + { + krb5_pa_s4u_x509_user s4u; + setup(s4u,krb5_pa_s4u_x509_user,"pa_s4u_x509_user", + ktest_make_sample_pa_s4u_x509_user); + encode_run(s4u,krb5_pa_s4u_x509_user, + "pa_s4u_x509_user","", + encode_krb5_pa_s4u_x509_user); + ktest_empty_pa_s4u_x509_user(&s4u); + } + #ifdef ENABLE_LDAP { ldap_seqof_key_data skd; diff --git a/src/tests/asn.1/ktest.c b/src/tests/asn.1/ktest.c index 5951b6c7e..8b6367918 100644 --- a/src/tests/asn.1/ktest.c +++ b/src/tests/asn.1/ktest.c @@ -825,6 +825,23 @@ krb5_error_code ktest_make_sample_enc_sam_response_enc_2(p) return 0; } +krb5_error_code ktest_make_sample_pa_s4u_x509_user(p) + krb5_pa_s4u_x509_user *p; +{ + krb5_error_code retval; + krb5_s4u_userid *u = &p->user_id; + u->nonce = 13243546; + retval = ktest_make_sample_principal(&u->user); + if (retval) return retval; + u->subject_cert.data = strdup("pa_s4u_x509_user"); + if (u->subject_cert.data == NULL) return ENOMEM; + u->subject_cert.length = strlen(u->subject_cert.data); + u->options = 0x80000000; + retval = ktest_make_sample_checksum(&p->cksum); + if (retval) return retval; + return 0; +} + #ifdef ENABLE_LDAP static krb5_error_code ktest_make_sample_key_data(krb5_key_data *p, int i) { @@ -1420,6 +1437,14 @@ void ktest_empty_enc_sam_response_enc_2(p) ktest_empty_data(&p->sam_sad); } +void ktest_empty_pa_s4u_x509_user(p) + krb5_pa_s4u_x509_user *p; +{ + ktest_destroy_principal(&p->user_id.user); + ktest_empty_data(&p->user_id.subject_cert); + if (p->cksum.contents) free(p->cksum.contents); +} + #ifdef ENABLE_LDAP void ktest_empty_ldap_seqof_key_data(ctx, p) krb5_context ctx; diff --git a/src/tests/asn.1/ktest.h b/src/tests/asn.1/ktest.h index af7c9acc8..a2951d26f 100644 --- a/src/tests/asn.1/ktest.h +++ b/src/tests/asn.1/ktest.h @@ -105,7 +105,7 @@ krb5_error_code ktest_make_sample_enc_sam_response_enc (krb5_enc_sam_response_enc *p); krb5_error_code ktest_make_sample_predicted_sam_response(krb5_predicted_sam_response *p); krb5_error_code ktest_make_sample_enc_sam_response_enc_2(krb5_enc_sam_response_enc_2 *p); - +krb5_error_code ktest_make_sample_pa_s4u_x509_user(krb5_pa_s4u_x509_user *p); #ifdef ENABLE_LDAP krb5_error_code ktest_make_sample_ldap_seqof_key_data(ldap_seqof_key_data * p); @@ -214,6 +214,7 @@ void ktest_empty_enc_sam_response_enc(krb5_enc_sam_response_enc *p); void ktest_empty_predicted_sam_response(krb5_predicted_sam_response *p); void ktest_empty_sam_response_2(krb5_sam_response_2 *p); void ktest_empty_enc_sam_response_enc_2(krb5_enc_sam_response_enc_2 *p); +void ktest_empty_pa_s4u_x509_user(krb5_pa_s4u_x509_user *p); #ifdef ENABLE_LDAP void ktest_empty_ldap_seqof_key_data(krb5_context, ldap_seqof_key_data *p); diff --git a/src/tests/asn.1/ktest_equal.c b/src/tests/asn.1/ktest_equal.c index 5ec0a01dc..da0324973 100644 --- a/src/tests/asn.1/ktest_equal.c +++ b/src/tests/asn.1/ktest_equal.c @@ -542,6 +542,20 @@ int ktest_equal_sam_response(ref, var) return p; } +int ktest_equal_pa_s4u_x509_user(ref, var) + krb5_pa_s4u_x509_user *ref; + krb5_pa_s4u_x509_user *var; +{ + int p = TRUE; + if (ref == var) return TRUE; + else if (ref == NULL || var == NULL) return FALSE; + p=p&&scalar_equal(user_id.nonce); + p=p&&ptr_equal(user_id.user,ktest_equal_principal_data); + p=p&&struct_equal(user_id.subject_cert,ktest_equal_data); + p=p&&scalar_equal(user_id.options); + p=p&&struct_equal(cksum,ktest_equal_checksum); + return p; +} #ifdef ENABLE_LDAP static int equal_key_data(ref, var) krb5_key_data *ref; diff --git a/src/tests/asn.1/ktest_equal.h b/src/tests/asn.1/ktest_equal.h index 217272378..8a0641de5 100644 --- a/src/tests/asn.1/ktest_equal.h +++ b/src/tests/asn.1/ktest_equal.h @@ -91,6 +91,10 @@ int ktest_equal_krb5_etype_info_entry (krb5_etype_info_entry * ref, krb5_etype_info_entry * var); +int ktest_equal_pa_s4u_x509_user + (krb5_pa_s4u_x509_user *ref, + krb5_pa_s4u_x509_user *var); + int ktest_equal_ldap_sequence_of_keys(ldap_seqof_key_data *ref, ldap_seqof_key_data *var); #endif diff --git a/src/tests/asn.1/reference_encode.out b/src/tests/asn.1/reference_encode.out index b6ac7fb2d..0d913cdb2 100644 --- a/src/tests/asn.1/reference_encode.out +++ b/src/tests/asn.1/reference_encode.out @@ -56,3 +56,4 @@ encode_krb5_enc_sam_response_enc: 30 38 A0 05 02 03 01 33 2A A1 11 18 0F 31 39 3 encode_krb5_predicted_sam_response: 30 6D A0 13 30 11 A0 03 02 01 01 A1 0A 04 08 31 32 33 34 35 36 37 38 A1 07 03 05 00 00 00 00 09 A2 11 18 0F 31 39 37 30 30 31 30 31 30 30 30 30 31 37 5A A3 03 02 01 12 A4 10 1B 0E 41 54 48 45 4E 41 2E 4D 49 54 2E 45 44 55 A5 1A 30 18 A0 03 02 01 01 A1 11 30 0F 1B 06 68 66 74 73 61 69 1B 05 65 78 74 72 61 A6 07 04 05 68 65 6C 6C 6F encode_krb5_sam_response_2: 30 42 A0 03 02 01 2B A1 07 03 05 00 80 00 00 00 A2 0C 04 0A 74 72 61 63 6B 20 64 61 74 61 A3 1D 30 1B A0 03 02 01 01 A1 04 02 02 0D 36 A2 0E 04 0C 6E 6F 6E 63 65 20 6F 72 20 73 61 64 A4 05 02 03 54 32 10 encode_krb5_enc_sam_response_enc_2: 30 1F A0 03 02 01 58 A1 18 04 16 65 6E 63 5F 73 61 6D 5F 72 65 73 70 6F 6E 73 65 5F 65 6E 63 5F 32 +encode_krb5_pa_s4u_x509_user: 30 68 A0 55 30 53 A0 06 02 04 00 CA 14 9A A1 1A 30 18 A0 03 02 01 01 A1 11 30 0F 1B 06 68 66 74 73 61 69 1B 05 65 78 74 72 61 A2 10 1B 0E 41 54 48 45 4E 41 2E 4D 49 54 2E 45 44 55 A3 12 04 10 70 61 5F 73 34 75 5F 78 35 30 39 5F 75 73 65 72 A4 07 03 05 00 80 00 00 00 A1 0F 30 0D A0 03 02 01 01 A1 06 04 04 31 32 33 34 diff --git a/src/tests/asn.1/trval_reference.out b/src/tests/asn.1/trval_reference.out index 9c5f8cc1e..c8aa48e3f 100644 --- a/src/tests/asn.1/trval_reference.out +++ b/src/tests/asn.1/trval_reference.out @@ -1246,3 +1246,20 @@ encode_krb5_enc_sam_response_enc_2: . [0] [Integer] 88 . [1] [Octet String] "enc_sam_response_enc_2" +encode_krb5_pa_s4u_x509_user: + +[Sequence/Sequence Of] +. [0] [Sequence/Sequence Of] +. . [0] [Integer] 13243546 +. . [1] [Sequence/Sequence Of] +. . . [0] [Integer] 1 +. . . [1] [Sequence/Sequence Of] +. . . . [General string] "hftsai" +. . . . [General string] "extra" +. . [2] [General string] "ATHENA.MIT.EDU" +. . [3] [Octet String] "pa_s4u_x509_user" +. . [4] [Bit String] 0x80000000 +. [1] [Sequence/Sequence Of] +. . [0] [Integer] 1 +. . [1] [Octet String] "1234" + diff --git a/src/tests/gssapi/Makefile.in b/src/tests/gssapi/Makefile.in index d0ea1e137..e385c68f6 100644 --- a/src/tests/gssapi/Makefile.in +++ b/src/tests/gssapi/Makefile.in @@ -6,15 +6,18 @@ DEFINES = -DUSE_AUTOCONF_H PROG_LIBPATH=-L$(TOPLIBD) PROG_RPATH=$(KRB5_LIBDIR) -SRCS= $(srcdir)/t_imp_name.c +SRCS= $(srcdir)/t_imp_name.c $(srcdir)/t_s4u.c -OBJS= t_imp_name.o +OBJS= t_imp_name.o t_s4u.o -all:: t_imp_name +all:: t_imp_name t_s4u t_imp_name: t_imp_name.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS) $(CC_LINK) -o t_imp_name t_imp_name.o $(GSS_LIBS) $(KRB5_BASE_LIBS) +t_s4u: t_s4u.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS) + $(CC_LINK) -o t_s4u t_s4u.o $(GSS_LIBS) $(KRB5_BASE_LIBS) + clean:: - $(RM) t_imp_name + $(RM) t_imp_name t_s4u diff --git a/src/tests/gssapi/t_s4u.c b/src/tests/gssapi/t_s4u.c new file mode 100644 index 000000000..264e60a60 --- /dev/null +++ b/src/tests/gssapi/t_s4u.c @@ -0,0 +1,418 @@ +/* -*- mode: c; indent-tabs-mode: nil -*- */ +/* + * Copyright 2009 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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <gssapi/gssapi_krb5.h> + +/* + * Test program for protocol transition (S4U2Self) and constrained delegation + * (S4U2Proxy) + * + * Note: because of name canonicalization, the following tips may help + * when configuring with Active Directory: + * + * - Create a computer account FOO$ + * - Set the UPN to host/foo.domain (no suffix); this is necessary to + * be able to send an AS-REQ as this principal, otherwise you would + * need to use the canonical name (FOO$), which will cause principal + * comparison errors in gss_accept_sec_context(). + * - Add a SPN of host/foo.domain + * - Configure the computer account to support constrained delegation with + * protocol transition (Trust this computer for delegation to specified + * services only / Use any authentication protocol) + * - Add host/foo.domain to the keytab (possibly easiest to do this + * with ktadd) + * + * For S4U2Proxy to work the TGT must be forwardable too. + * + * Usage eg: + * + * kinit -k -t test.keytab -f 'host/test.win.mit.edu@WIN.MIT.EDU' + * ./t_s4u delegtest@WIN.MIT.EDU HOST/WIN-EQ7E4AA2WR8.win.mit.edu@WIN.MIT.EDU test.keytab + */ + +static gss_OID_desc spnego_mech = { 6, "\053\006\001\005\005\002" }; + +int use_spnego = 0; + +static void displayStatus_1(m, code, type) + char *m; + OM_uint32 code; + int type; +{ + OM_uint32 maj_stat, min_stat; + gss_buffer_desc msg; + OM_uint32 msg_ctx; + + msg_ctx = 0; + while (1) { + maj_stat = gss_display_status(&min_stat, code, + type, GSS_C_NULL_OID, + &msg_ctx, &msg); + fprintf(stderr, "%s: %s\n", m, (char *)msg.value); + (void) gss_release_buffer(&min_stat, &msg); + + if (!msg_ctx) + break; + } +} + +static void displayStatus(msg, maj_stat, min_stat) + char *msg; + OM_uint32 maj_stat; + OM_uint32 min_stat; +{ + displayStatus_1(msg, maj_stat, GSS_C_GSS_CODE); + displayStatus_1(msg, min_stat, GSS_C_MECH_CODE); +} + +static OM_uint32 +displayCanonName(OM_uint32 *minor, gss_name_t name, char *tag) +{ + gss_name_t canon; + OM_uint32 major, tmp_minor; + gss_buffer_desc buf; + + major = gss_canonicalize_name(minor, name, + (gss_OID)gss_mech_krb5, &canon); + if (GSS_ERROR(major)) { + displayStatus("gss_canonicalize_name", major, *minor); + return major; + } + + major = gss_display_name(minor, canon, &buf, NULL); + if (GSS_ERROR(major)) { + displayStatus("gss_display_name", major, *minor); + gss_release_name(&tmp_minor, &canon); + return major; + } + + printf("%s:\t%s\n", tag, (char *)buf.value); + + gss_release_buffer(&tmp_minor, &buf); + gss_release_name(&tmp_minor, &canon); + + return GSS_S_COMPLETE; +} + +static OM_uint32 +displayOID(OM_uint32 *minor, gss_OID oid, char *tag) +{ + OM_uint32 major, tmp_minor; + gss_buffer_desc buf; + + major = gss_oid_to_str(minor, oid, &buf); + if (GSS_ERROR(major)) { + displayStatus("gss_oid_to_str", major, *minor); + return major; + } + + printf("%s:\t%s\n", tag, (char *)buf.value); + + gss_release_buffer(&tmp_minor, &buf); + + return GSS_S_COMPLETE; +} + +static OM_uint32 +initAcceptSecContext(OM_uint32 *minor, + gss_cred_id_t claimant_cred_handle, + gss_cred_id_t verifier_cred_handle, + gss_cred_id_t *deleg_cred_handle) +{ + OM_uint32 major, tmp_minor; + gss_buffer_desc token, tmp; + gss_ctx_id_t initiator_context = GSS_C_NO_CONTEXT; + gss_ctx_id_t acceptor_context = GSS_C_NO_CONTEXT; + gss_name_t source_name = GSS_C_NO_NAME; + gss_name_t target_name = GSS_C_NO_NAME; + OM_uint32 time_rec; + gss_OID mech = GSS_C_NO_OID; + + token.value = NULL; + token.length = 0; + + tmp.value = NULL; + tmp.length = 0; + + *deleg_cred_handle = GSS_C_NO_CREDENTIAL; + + major = gss_inquire_cred(minor, verifier_cred_handle, + &target_name, NULL, NULL, NULL); + if (GSS_ERROR(major)) { + displayStatus("gss_inquire_cred", major, *minor); + return major; + } + + displayCanonName(minor, target_name, "Target name"); + + mech = use_spnego ? (gss_OID)&spnego_mech : (gss_OID)gss_mech_krb5; + displayOID(minor, mech, "Target mech"); + + major = gss_init_sec_context(minor, + claimant_cred_handle, + &initiator_context, + target_name, + mech, + GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG, + GSS_C_INDEFINITE, + GSS_C_NO_CHANNEL_BINDINGS, + GSS_C_NO_BUFFER, + NULL, + &token, + NULL, + &time_rec); + + if (target_name != GSS_C_NO_NAME) + (void) gss_release_name(&tmp_minor, &target_name); + + if (GSS_ERROR(major)) { + displayStatus("gss_init_sec_context", major, *minor); + return major; + } + + (void) gss_delete_sec_context(minor, &initiator_context, NULL); + mech = GSS_C_NO_OID; + + major = gss_accept_sec_context(minor, + &acceptor_context, + verifier_cred_handle, + &token, + GSS_C_NO_CHANNEL_BINDINGS, + &source_name, + &mech, + &tmp, + NULL, + &time_rec, + deleg_cred_handle); + + if (GSS_ERROR(major)) + displayStatus("gss_accept_sec_context", major, *minor); + else { + displayCanonName(minor, source_name, "Source name"); + displayOID(minor, mech, "Source mech"); + } + + (void) gss_release_name(&tmp_minor, &source_name); + (void) gss_delete_sec_context(&tmp_minor, &acceptor_context, NULL); + (void) gss_release_buffer(&tmp_minor, &token); + (void) gss_release_buffer(&tmp_minor, &tmp); + (void) gss_release_oid(&tmp_minor, &mech); + + return major; +} + +static OM_uint32 +constrainedDelegate(OM_uint32 *minor, + gss_OID_set desired_mechs, + gss_name_t target, + gss_cred_id_t delegated_cred_handle, + gss_cred_id_t verifier_cred_handle) +{ + OM_uint32 major, tmp_minor; + gss_ctx_id_t initiator_context = GSS_C_NO_CONTEXT; + gss_name_t cred_name = GSS_C_NO_NAME; + OM_uint32 time_rec, lifetime; + gss_cred_usage_t usage; + gss_buffer_desc token; + gss_OID_set mechs; + + printf("Constrained delegation tests follow\n"); + printf("-----------------------------------\n\n"); + + if (gss_inquire_cred(minor, verifier_cred_handle, &cred_name, + &lifetime, &usage, NULL) == GSS_S_COMPLETE) { + displayCanonName(minor, cred_name, "Proxy name"); + gss_release_name(&tmp_minor, &cred_name); + } + displayCanonName(minor, target, "Target name"); + if (gss_inquire_cred(minor, delegated_cred_handle, &cred_name, + &lifetime, &usage, &mechs) == GSS_S_COMPLETE) { + displayCanonName(minor, cred_name, "Delegated name"); + displayOID(minor, &mechs->elements[0], "Delegated mech"); + gss_release_name(&tmp_minor, &cred_name); + } + + printf("\n"); + + major = gss_init_sec_context(minor, + delegated_cred_handle, + &initiator_context, + target, + mechs ? &mechs->elements[0] : + (gss_OID)gss_mech_krb5, + GSS_C_REPLAY_FLAG | GSS_C_SEQUENCE_FLAG, + GSS_C_INDEFINITE, + GSS_C_NO_CHANNEL_BINDINGS, + GSS_C_NO_BUFFER, + NULL, + &token, + NULL, + &time_rec); + if (GSS_ERROR(major)) + displayStatus("gss_init_sec_context", major, *minor); + + (void) gss_release_buffer(&tmp_minor, &token); + (void) gss_delete_sec_context(&tmp_minor, &initiator_context, NULL); + (void) gss_release_oid_set(&tmp_minor, &mechs); + + return major; +} + +int main(int argc, char *argv[]) +{ + OM_uint32 minor, major; + gss_cred_id_t impersonator_cred_handle = GSS_C_NO_CREDENTIAL; + gss_cred_id_t user_cred_handle = GSS_C_NO_CREDENTIAL; + gss_cred_id_t delegated_cred_handle = GSS_C_NO_CREDENTIAL; + gss_name_t user = GSS_C_NO_NAME, target = GSS_C_NO_NAME; + gss_OID_set_desc mechs; + gss_OID_set actual_mechs = GSS_C_NO_OID_SET; + gss_buffer_desc buf; + + if (argc < 2 || argc > 5) { + fprintf(stderr, "Usage: %s [--spnego] [user] " + "[proxy-target] [keytab]\n", argv[0]); + fprintf(stderr, " proxy-target and keytab are optional\n"); + exit(1); + } + + if (strcmp(argv[1], "--spnego") == 0) { + use_spnego++; + argc--; + argv++; + } + + buf.value = argv[1]; + buf.length = strlen((char *)buf.value); + + major = gss_import_name(&minor, &buf, + (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, + &user); + if (GSS_ERROR(major)) { + displayStatus("gss_import_name(user)", major, minor); + goto out; + } + + if (argc > 2 && strcmp(argv[2], "-")) { + buf.value = argv[2]; + buf.length = strlen((char *)buf.value); + + major = gss_import_name(&minor, &buf, + (gss_OID)GSS_KRB5_NT_PRINCIPAL_NAME, + &target); + if (GSS_ERROR(major)) { + displayStatus("gss_import_name(target)", major, minor); + goto out; + } + } else { + target = GSS_C_NO_NAME; + } + + if (argc > 3) { + major = krb5_gss_register_acceptor_identity(argv[3]); + if (GSS_ERROR(major)) { + displayStatus("krb5_gss_register_acceptor_identity", + major, minor); + goto out; + } + } + + mechs.elements = use_spnego ? (gss_OID)&spnego_mech : + (gss_OID)gss_mech_krb5; + mechs.count = 1; + + /* get default cred */ + major = gss_acquire_cred(&minor, + GSS_C_NO_NAME, + GSS_C_INDEFINITE, + &mechs, + GSS_C_BOTH, + &impersonator_cred_handle, + &actual_mechs, + NULL); + if (GSS_ERROR(major)) { + displayStatus("gss_acquire_cred", major, minor); + goto out; + } + + (void) gss_release_oid_set(&minor, &actual_mechs); + + printf("Protocol transition tests follow\n"); + printf("-----------------------------------\n\n"); + + /* get S4U2Self cred */ + major = gss_acquire_cred_impersonate_name(&minor, + impersonator_cred_handle, + user, + GSS_C_INDEFINITE, + &mechs, + GSS_C_INITIATE, + &user_cred_handle, + &actual_mechs, + NULL); + if (GSS_ERROR(major)) { + displayStatus("gss_acquire_cred_impersonate_name", major, minor); + goto out; + } + + major = initAcceptSecContext(&minor, + user_cred_handle, + impersonator_cred_handle, + &delegated_cred_handle); + if (GSS_ERROR(major)) + goto out; + + printf("\n"); + + if (target != GSS_C_NO_NAME && + delegated_cred_handle != GSS_C_NO_CREDENTIAL) { + major = constrainedDelegate(&minor, &mechs, target, + delegated_cred_handle, + impersonator_cred_handle); + } else if (target != GSS_C_NO_NAME) { + fprintf(stderr, "Warning: no delegated credentials handle returned\n\n"); + fprintf(stderr, "Verify:\n\n"); + fprintf(stderr, " - The TGT for the impersonating service is forwardable\n"); + fprintf(stderr, " - The T2A4D flag set on the impersonating service's UAC\n"); + fprintf(stderr, " - The user is not marked sensitive and cannot be delegated\n"); + fprintf(stderr, "\n"); + } + +out: + (void) gss_release_name(&minor, &user); + (void) gss_release_name(&minor, &target); + (void) gss_release_cred(&minor, &delegated_cred_handle); + (void) gss_release_cred(&minor, &impersonator_cred_handle); + (void) gss_release_cred(&minor, &user_cred_handle); + (void) gss_release_oid_set(&minor, &actual_mechs); + + return GSS_ERROR(major) ? 1 : 0; +} + |