Skip to content

Commit

Permalink
Add KDC support for RBCD requests
Browse files Browse the repository at this point in the history
Add two new KDB methods to support resource-based constrained
delegation.  The get_authdata_info method extracts the client
principal for the authdata (necessary for cross-realm RBCD requests as
the evidence ticket is a cross-realm TGT with the client's authdata),
and also returns an opaque pointer for consumption by other KDB
methods.  The allowed_to_delegate_from method performs a constrained
delegation policy check on the principal entry of the target
principal.

Add the server principal and abstract authdata representation to the
sign_authdata method.  (XXX the DAL major version and KDB API version
need to be bumped; this will be handled in the merge with PR krb5#965.)

Add core KDC code for RBCD requests.  For local RBCD requests
(impersonator and target in the same realm), KDC handling is similar
to existing constrained delegation support.  The evidence ticket is
not required to be forwardable, and allowed_to_delegate_from is used
in preference to check_allowed_to_delegate.

For cross-realm RBCD requests, the KDC could be in the impersonator
realm, the target realm, or in a transit realm between the two.  In
the transit realm case, the request looks like a regular cross-realm
request for a krbtgt service except for the information in the PAC, so
this case is handled by the KDB module sign_authdata() method.

[ghudson@mit.edu: made style and documentation edits, and edited
commit message]
  • Loading branch information
iboukris authored and greghudson committed Aug 14, 2019
1 parent 12d8fcb commit 97be191
Show file tree
Hide file tree
Showing 9 changed files with 367 additions and 47 deletions.
96 changes: 95 additions & 1 deletion src/include/kdb.h
Expand Up @@ -128,6 +128,8 @@
#define KRB5_KDB_FLAG_CROSS_REALM 0x00001000
/* Allow in-realm aliases */
#define KRB5_KDB_FLAG_ALIAS_OK 0x00002000
/* Issuing referral */
#define KRB5_KDB_FLAG_ISSUING_REFERRAL 0x00004000

#define KRB5_KDB_FLAGS_S4U ( KRB5_KDB_FLAG_PROTOCOL_TRANSITION | \
KRB5_KDB_FLAG_CONSTRAINED_DELEGATION )
Expand Down Expand Up @@ -216,6 +218,17 @@ typedef struct _krb5_db_entry_new {
krb5_key_data * key_data; /* Array */
} krb5_db_entry;

/*
* A KDB module's representation of authorization data. Modules may return
* this from the get_authdata_info method and consume it in other methods to
* avoid repeated decoding.
*/
typedef struct _krb5_ad_entry {
krb5_principal ad_princ;
void *opaque;
void (*free_opaque)(krb5_context context, void *opaque);
} krb5_ad_entry;

typedef struct _osa_policy_ent_t {
int version;
char *name;
Expand Down Expand Up @@ -663,6 +676,7 @@ krb5_db_get_key_data_kvno( krb5_context context,
krb5_error_code krb5_db_sign_authdata(krb5_context kcontext,
unsigned int flags,
krb5_const_principal client_princ,
krb5_const_principal server_princ,
krb5_db_entry *client,
krb5_db_entry *server,
krb5_db_entry *krbtgt,
Expand All @@ -672,6 +686,7 @@ krb5_error_code krb5_db_sign_authdata(krb5_context kcontext,
krb5_keyblock *session_key,
krb5_timestamp authtime,
krb5_authdata **tgt_auth_data,
krb5_ad_entry *ad_info,
krb5_authdata ***signed_auth_data);

krb5_error_code krb5_db_check_transited_realms(krb5_context kcontext,
Expand Down Expand Up @@ -713,6 +728,22 @@ krb5_error_code krb5_db_get_s4u_x509_principal(krb5_context kcontext,
unsigned int flags,
krb5_db_entry **entry);

krb5_error_code krb5_db_allowed_to_delegate_from(krb5_context context,
krb5_const_principal client,
krb5_const_principal server,
krb5_ad_entry *server_ad_info,
const krb5_db_entry *proxy);

krb5_error_code krb5_db_get_authdata_info(krb5_context context,
unsigned int flags,
krb5_authdata **in_authdata,
krb5_const_principal client_princ,
krb5_const_principal server_princ,
krb5_keyblock *server_key,
krb5_db_entry *krbtgt,
krb5_timestamp authtime,
krb5_ad_entry **ad_info);

/**
* Sort an array of @a krb5_key_data keys in descending order by their kvno.
* Key data order within a kvno is preserved.
Expand Down Expand Up @@ -845,6 +876,8 @@ krb5_dbe_free_strings(krb5_context, krb5_string_attr *, int count);
void
krb5_dbe_free_string(krb5_context, char *);

void krb5_free_ad_entry(krb5_context kcontext, krb5_ad_entry *ad_entry);

/*
* Register the KDB keytab type, allowing "KDB:" to be used as a keytab name.
* For this type to work, the context used for keytab operations must have an
Expand Down Expand Up @@ -1017,7 +1050,13 @@ typedef struct _kdb_vftabl {
*
* KRB5_KDB_FLAG_CROSS_REALM: Set by the KDC when looking up a client entry
* during a TGS request, if the client principal is not part of the
* realm being served.
* realm being served. It is also set for a final cross-realm S4U2Self
* request, in which a server queries its own realm using a cross-realm
* TGT.
*
* KRB5_KDB_FLAG_ISSUING_REFERRAL: Set by the KDC when looking up a server
* entry during a TGS request, if the server principal is not part of
* the realm being served, and a referral will be issued instead.
*
* KRB5_KDB_FLAG_ALIAS_OK: Set by the KDC for server principal lookups and
* for AS request client principal lookups with canonicalization
Expand Down Expand Up @@ -1268,6 +1307,8 @@ typedef struct _kdb_vftabl {
* principal requested by the service; for regular TGS requests, the
* possibly-canonicalized client principal.
*
* server_princ: The server principal in the request.
*
* client: The DB entry of the client. For S4U2Self, this will be the DB
* entry for the client principal requested by the service).
*
Expand Down Expand Up @@ -1301,10 +1342,15 @@ typedef struct _kdb_vftabl {
*
* tgt_auth_data: For TGS requests, the authorization data present in the
* subject ticket. For AS requests, NULL.
*
* ad_info: For TGS requests, the parsed authorization data if obtained
* by get_authdata_info method from the authorization data present in
* the subject ticket. NULL, otherwise.
*/
krb5_error_code (*sign_authdata)(krb5_context kcontext,
unsigned int flags,
krb5_const_principal client_princ,
krb5_const_principal server_princ,
krb5_db_entry *client,
krb5_db_entry *server,
krb5_db_entry *krbtgt,
Expand All @@ -1314,6 +1360,7 @@ typedef struct _kdb_vftabl {
krb5_keyblock *session_key,
krb5_timestamp authtime,
krb5_authdata **tgt_auth_data,
krb5_ad_entry *ad_info,
krb5_authdata ***signed_auth_data);

/*
Expand Down Expand Up @@ -1424,6 +1471,53 @@ typedef struct _kdb_vftabl {
krb5_db_entry **entry_out);

/* End of minor version 1 for major version 7. */

/*
* Optional: Perform a policy check on server being allowed to obtain
* tickets from client to proxy. This method is similar to
* check_allowed_to_delegate, but it operates on the target server DB entry
* (called "proxy" here as per Microsoft's protocol documentation) rather
* than the intermediate server entry. server_ad_info represents the
* authdata of the intermediate server, as returned by the
* get_authdata_info method on the header ticket. Return 0 if policy
* allows it, or an appropriate error (such as KRB5KDC_ERR_POLICY) if not.
*
* This method is called for S4U2Proxy requests and implements resource-
* based constrained delegation (cross-realm S4U2Proxy). If this method is
* not implemented or if it returns a policy error, the KDC will fall back
* to check_allowed_to_delegate if the intermediate and target servers are
* in the same realm and the evidence ticket is forwardable.
*/
krb5_error_code (*allowed_to_delegate_from)(krb5_context context,
krb5_const_principal client,
krb5_const_principal server,
krb5_ad_entry *server_ad_info,
const krb5_db_entry *proxy);

/*
* Optional: Perform verification and policy checks on authorization data,
* such as a Windows PAC, based on the request client lookup flags. Return
* 0 if all checks have passed. Optionally return a representation of the
* authdata in ad_info. If the method sets ad_info->ad_princ is set, it
* indicates the client principal for a cross-realm S4U2Proxy request.
*
* This method is called for TGS requests on the authorization data from
* the header ticket. For S4U2Proxy requests it is also called on the
* authorization data from the evidence ticket. If the
* KRB5_KDB_FLAG_PROTOCOL_TRANSITION bit is set in flags, the authdata is
* from the header ticket of an S4U2Self referral request, and the supplied
* client_princ will be the requested client.
*/
krb5_error_code (*get_authdata_info)(krb5_context context,
unsigned int flags,
krb5_authdata **in_authdata,
krb5_const_principal client_princ,
krb5_const_principal server_princ,
krb5_keyblock *server_key,
krb5_db_entry *krbtgt,
krb5_timestamp authtime,
krb5_ad_entry **ad_info);

} kdb_vftabl;

#endif /* !defined(_WIN32) */
Expand Down
1 change: 1 addition & 0 deletions src/kdc/do_as_req.c
Expand Up @@ -306,6 +306,7 @@ finish_process_as_req(struct as_req_state *state, krb5_error_code errcode)
state->req_pkt,
state->request,
NULL, /* for_user_princ */
NULL, /* ad_info */
NULL, /* enc_tkt_request */
state->auth_indicators,
&state->enc_tkt_reply);
Expand Down
80 changes: 59 additions & 21 deletions src/kdc/do_tgs_req.c
Expand Up @@ -79,7 +79,7 @@ prepare_error_tgs(struct kdc_request_state *, krb5_kdc_req *,krb5_ticket *,int,

static krb5_error_code
decrypt_2ndtkt(kdc_realm_t *, krb5_kdc_req *, krb5_flags, krb5_db_entry **,
const char **);
krb5_keyblock **, const char **);

static krb5_error_code
gen_session_key(kdc_realm_t *, krb5_kdc_req *, krb5_db_entry *,
Expand All @@ -104,6 +104,7 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
{
krb5_keyblock * subkey = 0;
krb5_keyblock *header_key = NULL;
krb5_keyblock *stkt_server_key = NULL;
krb5_db_entry *server = NULL;
krb5_db_entry *stkt_server = NULL;
krb5_kdc_rep reply;
Expand All @@ -119,6 +120,7 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
krb5_keyblock *reply_key = NULL;
krb5_key_data *server_key;
krb5_principal cprinc = NULL, sprinc = NULL, altcprinc = NULL;
krb5_const_principal authdata_client;
krb5_last_req_entry *nolrarray[2], nolrentry;
int errcode;
const char *status = 0;
Expand All @@ -138,6 +140,7 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
krb5_pa_data **e_data = NULL;
krb5_audit_state *au_state = NULL;
krb5_data **auth_indicators = NULL;
krb5_ad_entry *ad_info = NULL, *stkt_ad_info = NULL;

memset(&reply, 0, sizeof(reply));
memset(&reply_encpart, 0, sizeof(reply_encpart));
Expand Down Expand Up @@ -247,6 +250,8 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
* issuing a referral (or alternate TGT, which we treat similarly). */
is_referral = is_cross_tgs_principal(server->princ) &&
!krb5_principal_compare(kdc_context, request->server, server->princ);
if (is_referral)
setflag(c_flags, KRB5_KDB_FLAG_ISSUING_REFERRAL);

au_state->stage = VALIDATE_POL;

Expand Down Expand Up @@ -290,24 +295,53 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,

if (errcode)
goto cleanup;

if (s4u_x509_user != NULL && client == NULL) {
/*
* For an S4U2Self referral request (the client is following a referral
* back to the server realm), the authdata in the header ticket should
* be for the requested client. Also, the request is cross-realm even
* if the header ticket client is a local principal.
*/
setflag(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION);
setflag(c_flags, KRB5_KDB_FLAG_CROSS_REALM);
authdata_client = s4u_x509_user->user_id.user;
} else {
/* Otherwise (including for initial S4U2Self requests), the authdata
* should be for the header ticket client. */
authdata_client = header_enc_tkt->client;
}
errcode = krb5_db_get_authdata_info(kdc_context, c_flags,
header_enc_tkt->authorization_data,
authdata_client, request->server,
header_key, header_server,
header_enc_tkt->times.authtime,
&ad_info);
if (errcode && errcode != KRB5_PLUGIN_OP_NOTSUPP)
goto cleanup;

/* Flag all S4U2Self requests now that we have checked the authdata. */
if (s4u_x509_user != NULL)
setflag(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION);

/* Deal with user-to-user and constrained delegation */
errcode = decrypt_2ndtkt(kdc_active_realm, request, c_flags,
&stkt_server, &status);
&stkt_server, &stkt_server_key, &status);
if (errcode)
goto cleanup;

if (isflagset(request->kdc_options, KDC_OPT_CNAME_IN_ADDL_TKT)) {
/* Do constrained delegation protocol and authorization checks */
errcode = kdc_process_s4u2proxy_req(kdc_active_realm,
request,
setflag(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION);

errcode = kdc_process_s4u2proxy_req(kdc_active_realm, c_flags, request,
request->second_ticket[st_idx]->enc_part2,
stkt_server,
header_server, stkt_server,
stkt_server_key,
header_ticket->enc_part2->client,
request->server,
&status);
is_referral ? NULL : server,
request->server, ad_info,
&stkt_ad_info, &status);
if (errcode == KDC_ERR_POLICY || errcode == KDC_ERR_BADOPTION)
au_state->violation = PROT_CONSTRAINT;
else if (errcode)
Expand All @@ -323,8 +357,6 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
if (errcode)
goto cleanup;

setflag(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION);

assert(krb5_is_tgs_principal(header_ticket->server));

assert(client == NULL); /* assured by kdc_process_s4u2self_req() */
Expand Down Expand Up @@ -476,7 +508,10 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION)) {
altcprinc = s4u_x509_user->user_id.user;
} else if (isflagset(c_flags, KRB5_KDB_FLAG_CONSTRAINED_DELEGATION)) {
altcprinc = subject_tkt->client;
/* kdc_process_s4u2proxy_req() only allows cross-realm requests if
* stkt_ad_info->ad_princ is set. */
altcprinc = isflagset(c_flags, KRB5_KDB_FLAG_CROSS_REALM) ?
stkt_ad_info->ad_princ : subject_tkt->client;
} else {
altcprinc = NULL;
}
Expand Down Expand Up @@ -539,11 +574,10 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
}
}

if (isflagset(c_flags, KRB5_KDB_FLAG_PROTOCOL_TRANSITION) &&
!isflagset(c_flags, KRB5_KDB_FLAG_CROSS_REALM))
enc_tkt_reply.client = s4u_x509_user->user_id.user;
if (isflagset(c_flags, KRB5_KDB_FLAGS_S4U) && !is_referral)
enc_tkt_reply.client = altcprinc;
else
enc_tkt_reply.client = subject_tkt->client;
enc_tkt_reply.client = header_enc_tkt->client;

enc_tkt_reply.session = &session_key;
enc_tkt_reply.transited.tr_type = KRB5_DOMAIN_X500_COMPRESS;
Expand Down Expand Up @@ -621,8 +655,8 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
header_key,
pkt,
request,
s4u_x509_user ?
s4u_x509_user->user_id.user : NULL,
altcprinc,
stkt_ad_info ? stkt_ad_info : ad_info,
subject_tkt,
auth_indicators,
&enc_tkt_reply);
Expand Down Expand Up @@ -759,6 +793,8 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
krb5_free_keyblock_contents(kdc_context, &server_keyblock);
if (reply_key)
krb5_free_keyblock(kdc_context, reply_key);
if (stkt_server_key)
krb5_free_keyblock(kdc_context, stkt_server_key);
if (errcode)
emsg = krb5_get_error_message (kdc_context, errcode);

Expand Down Expand Up @@ -826,6 +862,10 @@ process_tgs_req(krb5_kdc_req *request, krb5_data *pkt,
krb5_free_authdata(kdc_context, enc_tkt_reply.authorization_data);
krb5_free_pa_data(kdc_context, e_data);
k5_free_data_ptr_list(auth_indicators);
if (ad_info)
krb5_free_ad_entry(kdc_context, ad_info);
if (stkt_ad_info)
krb5_free_ad_entry(kdc_context, stkt_ad_info);

return retval;
}
Expand Down Expand Up @@ -907,11 +947,10 @@ prepare_error_tgs (struct kdc_request_state *state,
static krb5_error_code
decrypt_2ndtkt(kdc_realm_t *kdc_active_realm, krb5_kdc_req *req,
krb5_flags flags, krb5_db_entry **server_out,
const char **status)
krb5_keyblock **key, const char **status)
{
krb5_error_code retval;
krb5_db_entry *server = NULL;
krb5_keyblock *key;
krb5_kvno kvno;
krb5_ticket *stkt;

Expand All @@ -923,15 +962,14 @@ decrypt_2ndtkt(kdc_realm_t *kdc_active_realm, krb5_kdc_req *req,
flags,
TRUE, /* match_enctype */
&server,
&key,
key,
&kvno);
if (retval != 0) {
*status = "2ND_TKT_SERVER";
goto cleanup;
}
retval = krb5_decrypt_tkt_part(kdc_context, key,
retval = krb5_decrypt_tkt_part(kdc_context, *key,
req->second_ticket[0]);
krb5_free_keyblock(kdc_context, key);
if (retval != 0) {
*status = "2ND_TKT_DECRYPT";
goto cleanup;
Expand Down

0 comments on commit 97be191

Please sign in to comment.