static krb5_error_code tgs_build_reply(krb5_context context, krb5_kdc_configuration *config, KDC_REQ *req, KDC_REQ_BODY *b, hdb_entry_ex *krbtgt, krb5_enctype krbtgt_etype, krb5_ticket *ticket, krb5_data *reply, const char *from, const char **e_text, AuthorizationData *auth_data, const struct sockaddr *from_addr, int datagram_reply) { krb5_error_code ret; krb5_principal cp = NULL, sp = NULL; krb5_principal client_principal = NULL; char *spn = NULL, *cpn = NULL; hdb_entry_ex *server = NULL, *client = NULL; EncTicketPart *tgt = &ticket->ticket; KRB5SignedPathPrincipals *spp = NULL; const EncryptionKey *ekey; krb5_keyblock sessionkey; krb5_kvno kvno; krb5_data rspac; int cross_realm = 0; PrincipalName *s; Realm r; int nloop = 0; EncTicketPart adtkt; char opt_str[128]; int require_signedpath = 0; memset(&sessionkey, 0, sizeof(sessionkey)); memset(&adtkt, 0, sizeof(adtkt)); krb5_data_zero(&rspac); s = b->sname; r = b->realm; if(b->kdc_options.enc_tkt_in_skey){ Ticket *t; hdb_entry_ex *uu; krb5_principal p; Key *uukey; if(b->additional_tickets == NULL || b->additional_tickets->len == 0){ ret = KRB5KDC_ERR_BADOPTION; /* ? */ kdc_log(context, config, 0, "No second ticket present in request"); goto out; } t = &b->additional_tickets->val[0]; if(!get_krbtgt_realm(&t->sname)){ kdc_log(context, config, 0, "Additional ticket is not a ticket-granting ticket"); ret = KRB5KDC_ERR_POLICY; goto out; } _krb5_principalname2krb5_principal(context, &p, t->sname, t->realm); ret = _kdc_db_fetch(context, config, p, HDB_F_GET_CLIENT|HDB_F_GET_SERVER, NULL, &uu); krb5_free_principal(context, p); if(ret){ if (ret == HDB_ERR_NOENTRY) ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; goto out; } ret = hdb_enctype2key(context, &uu->entry, t->enc_part.etype, &uukey); if(ret){ _kdc_free_ent(context, uu); ret = KRB5KDC_ERR_ETYPE_NOSUPP; /* XXX */ goto out; } ret = krb5_decrypt_ticket(context, t, &uukey->key, &adtkt, 0); _kdc_free_ent(context, uu); if(ret) goto out; ret = verify_flags(context, config, &adtkt, spn); if (ret) goto out; s = &adtkt.cname; r = adtkt.crealm; } _krb5_principalname2krb5_principal(context, &sp, *s, r); ret = krb5_unparse_name(context, sp, &spn); if (ret) goto out; _krb5_principalname2krb5_principal(context, &cp, tgt->cname, tgt->crealm); ret = krb5_unparse_name(context, cp, &cpn); if (ret) goto out; unparse_flags (KDCOptions2int(b->kdc_options), asn1_KDCOptions_units(), opt_str, sizeof(opt_str)); if(*opt_str) kdc_log(context, config, 0, "TGS-REQ %s from %s for %s [%s]", cpn, from, spn, opt_str); else kdc_log(context, config, 0, "TGS-REQ %s from %s for %s", cpn, from, spn); /* * Fetch server */ server_lookup: ret = _kdc_db_fetch(context, config, sp, HDB_F_GET_SERVER, NULL, &server); if(ret){ const char *new_rlm; Realm req_rlm; krb5_realm *realms; if ((req_rlm = get_krbtgt_realm(&sp->name)) != NULL) { if(nloop++ < 2) { new_rlm = find_rpath(context, tgt->crealm, req_rlm); if(new_rlm) { kdc_log(context, config, 5, "krbtgt for realm %s " "not found, trying %s", req_rlm, new_rlm); krb5_free_principal(context, sp); free(spn); krb5_make_principal(context, &sp, r, KRB5_TGS_NAME, new_rlm, NULL); ret = krb5_unparse_name(context, sp, &spn); if (ret) goto out; goto server_lookup; } } } else if(need_referral(context, sp, &realms)) { if (strcmp(realms[0], sp->realm) != 0) { kdc_log(context, config, 5, "Returning a referral to realm %s for " "server %s that was not found", realms[0], spn); krb5_free_principal(context, sp); free(spn); krb5_make_principal(context, &sp, r, KRB5_TGS_NAME, realms[0], NULL); ret = krb5_unparse_name(context, sp, &spn); if (ret) goto out; krb5_free_host_realm(context, realms); goto server_lookup; } krb5_free_host_realm(context, realms); } kdc_log(context, config, 0, "Server not found in database: %s: %s", spn, krb5_get_err_text(context, ret)); if (ret == HDB_ERR_NOENTRY) ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; goto out; } ret = _kdc_db_fetch(context, config, cp, HDB_F_GET_CLIENT, NULL, &client); if(ret) { const char *krbtgt_realm; /* * If the client belongs to the same realm as our krbtgt, it * should exist in the local database. * */ krbtgt_realm = krb5_principal_get_comp_string(context, krbtgt->entry.principal, 1); if(strcmp(krb5_principal_get_realm(context, cp), krbtgt_realm) == 0) { if (ret == HDB_ERR_NOENTRY) ret = KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN; kdc_log(context, config, 1, "Client no longer in database: %s", cpn); goto out; } kdc_log(context, config, 1, "Client not found in database: %s: %s", cpn, krb5_get_err_text(context, ret)); cross_realm = 1; } /* * Check that service is in the same realm as the krbtgt. If its * not the same, its someone that is using a uni-directional trust * backward. */ if (strcmp(krb5_principal_get_realm(context, sp), krb5_principal_get_comp_string(context, krbtgt->entry.principal, 1)) != 0) { char *tpn; ret = krb5_unparse_name(context, krbtgt->entry.principal, &tpn); kdc_log(context, config, 0, "Request with wrong krbtgt: %s", (ret == 0) ? tpn : "<unknown>"); if(ret == 0) free(tpn); ret = KRB5KRB_AP_ERR_NOT_US; goto out; } /* * */ client_principal = cp; if (client) { const PA_DATA *sdata; int i = 0; sdata = _kdc_find_padata(req, &i, KRB5_PADATA_S4U2SELF); if (sdata) { krb5_crypto crypto; krb5_data datack; PA_S4U2Self self; char *selfcpn = NULL; const char *str; ret = decode_PA_S4U2Self(sdata->padata_value.data, sdata->padata_value.length, &self, NULL); if (ret) { kdc_log(context, config, 0, "Failed to decode PA-S4U2Self"); goto out; } ret = _krb5_s4u2self_to_checksumdata(context, &self, &datack); if (ret) goto out; ret = krb5_crypto_init(context, &tgt->key, 0, &crypto); if (ret) { free_PA_S4U2Self(&self); krb5_data_free(&datack); kdc_log(context, config, 0, "krb5_crypto_init failed: %s", krb5_get_err_text(context, ret)); goto out; } ret = krb5_verify_checksum(context, crypto, KRB5_KU_OTHER_CKSUM, datack.data, datack.length, &self.cksum); krb5_data_free(&datack); krb5_crypto_destroy(context, crypto); if (ret) { free_PA_S4U2Self(&self); kdc_log(context, config, 0, "krb5_verify_checksum failed for S4U2Self: %s", krb5_get_err_text(context, ret)); goto out; } ret = _krb5_principalname2krb5_principal(context, &client_principal, self.name, self.realm); free_PA_S4U2Self(&self); if (ret) goto out; ret = krb5_unparse_name(context, client_principal, &selfcpn); if (ret) goto out; /* * Check that service doing the impersonating is * requesting a ticket to it-self. */ if (krb5_principal_compare(context, cp, sp) != TRUE) { kdc_log(context, config, 0, "S4U2Self: %s is not allowed " "to impersonate some other user " "(tried for user %s to service %s)", cpn, selfcpn, spn); free(selfcpn); ret = KRB5KDC_ERR_BADOPTION; /* ? */ goto out; } /* * If the service isn't trusted for authentication to * delegation, remove the forward flag. */ if (client->entry.flags.trusted_for_delegation) { str = "[forwardable]"; } else { b->kdc_options.forwardable = 0; str = ""; } kdc_log(context, config, 0, "s4u2self %s impersonating %s to " "service %s %s", cpn, selfcpn, spn, str); free(selfcpn); } } /* * Constrained delegation */ if (client != NULL && b->additional_tickets != NULL && b->additional_tickets->len != 0 && b->kdc_options.enc_tkt_in_skey == 0) { Key *clientkey; Ticket *t; char *str; t = &b->additional_tickets->val[0]; ret = hdb_enctype2key(context, &client->entry, t->enc_part.etype, &clientkey); if(ret){ ret = KRB5KDC_ERR_ETYPE_NOSUPP; /* XXX */ goto out; } ret = krb5_decrypt_ticket(context, t, &clientkey->key, &adtkt, 0); if (ret) { kdc_log(context, config, 0, "failed to decrypt ticket for " "constrained delegation from %s to %s ", spn, cpn); goto out; } /* check that ticket is valid */ if (adtkt.flags.forwardable == 0) { kdc_log(context, config, 0, "Missing forwardable flag on ticket for " "constrained delegation from %s to %s ", spn, cpn); ret = KRB5KDC_ERR_ETYPE_NOSUPP; /* XXX */ goto out; } ret = check_constrained_delegation(context, config, client, sp); if (ret) { kdc_log(context, config, 0, "constrained delegation from %s to %s not allowed", spn, cpn); goto out; } ret = _krb5_principalname2krb5_principal(context, &client_principal, adtkt.cname, adtkt.crealm); if (ret) goto out; ret = krb5_unparse_name(context, client_principal, &str); if (ret) goto out; ret = verify_flags(context, config, &adtkt, str); if (ret) { free(str); goto out; } /* * Check KRB5SignedPath in authorization data and add new entry to * make sure servers can't fake a ticket to us. */ ret = check_KRB5SignedPath(context, config, krbtgt, &adtkt, &spp, 1); if (ret) { kdc_log(context, config, 0, "KRB5SignedPath check from service %s failed " "for delegation to %s for client %s " "from %s failed with %s", spn, str, cpn, from, krb5_get_err_text(context, ret)); free(str); goto out; } kdc_log(context, config, 0, "constrained delegation for %s " "from %s to %s", str, cpn, spn); free(str); /* * Also require that the KDC have issue the service's krbtgt * used to do the request. */ require_signedpath = 1; } /* * Check flags */ ret = _kdc_check_flags(context, config, client, cpn, server, spn, FALSE); if(ret) goto out; if((b->kdc_options.validate || b->kdc_options.renew) && !krb5_principal_compare(context, krbtgt->entry.principal, server->entry.principal)){ kdc_log(context, config, 0, "Inconsistent request."); ret = KRB5KDC_ERR_SERVER_NOMATCH; goto out; } /* check for valid set of addresses */ if(!_kdc_check_addresses(context, config, tgt->caddr, from_addr)) { ret = KRB5KRB_AP_ERR_BADADDR; kdc_log(context, config, 0, "Request from wrong address"); goto out; } /* * Select enctype, return key and kvno. */ { krb5_enctype etype; if(b->kdc_options.enc_tkt_in_skey) { int i; ekey = &adtkt.key; for(i = 0; i < b->etype.len; i++) if (b->etype.val[i] == adtkt.key.keytype) break; if(i == b->etype.len) { krb5_clear_error_string(context); return KRB5KDC_ERR_ETYPE_NOSUPP; } etype = b->etype.val[i]; kvno = 0; } else { Key *skey; ret = _kdc_find_etype(context, server, b->etype.val, b->etype.len, &skey, &etype); if(ret) { kdc_log(context, config, 0, "Server (%s) has no support for etypes", spp); return ret; } ekey = &skey->key; kvno = server->entry.kvno; } ret = krb5_generate_random_keyblock(context, etype, &sessionkey); if (ret) goto out; } /* check PAC if not cross realm and if there is one */ if (!cross_realm) { Key *tkey; ret = hdb_enctype2key(context, &krbtgt->entry, krbtgt_etype, &tkey); if(ret) { kdc_log(context, config, 0, "Failed to find key for krbtgt PAC check"); goto out; } ret = check_PAC(context, config, client_principal, client, server, ekey, &tkey->key, tgt, &rspac, &require_signedpath); if (ret) { kdc_log(context, config, 0, "Verify PAC failed for %s (%s) from %s with %s", spn, cpn, from, krb5_get_err_text(context, ret)); goto out; } } /* also check the krbtgt for signature */ ret = check_KRB5SignedPath(context, config, krbtgt, tgt, &spp, require_signedpath); if (ret) { kdc_log(context, config, 0, "KRB5SignedPath check failed for %s (%s) from %s with %s", spn, cpn, from, krb5_get_err_text(context, ret)); goto out; } /* * */ ret = tgs_make_reply(context, config, b, client_principal, tgt, ekey, &sessionkey, kvno, auth_data, server, spn, client, cp, krbtgt, krbtgt_etype, spp, &rspac, e_text, reply); out: free(spn); free(cpn); krb5_data_free(&rspac); krb5_free_keyblock_contents(context, &sessionkey); if(server) _kdc_free_ent(context, server); if(client) _kdc_free_ent(context, client); if (client_principal && client_principal != cp) krb5_free_principal(context, client_principal); if (cp) krb5_free_principal(context, cp); if (sp) krb5_free_principal(context, sp); free_EncTicketPart(&adtkt); return ret; }
static krb5_error_code get_cred_kdc(krb5_context context, krb5_ccache id, krb5_kdc_flags flags, krb5_addresses *addresses, krb5_creds *in_creds, krb5_creds *krbtgt, krb5_principal impersonate_principal, Ticket *second_ticket, krb5_creds *out_creds) { TGS_REQ req; krb5_data enc; krb5_data resp; krb5_kdc_rep rep; KRB_ERROR error; krb5_error_code ret; unsigned nonce; krb5_keyblock *subkey = NULL; size_t len; Ticket second_ticket_data; METHOD_DATA padata; krb5_data_zero(&resp); krb5_data_zero(&enc); padata.val = NULL; padata.len = 0; krb5_generate_random_block(&nonce, sizeof(nonce)); nonce &= 0xffffffff; if(flags.b.enc_tkt_in_skey && second_ticket == NULL){ ret = decode_Ticket(in_creds->second_ticket.data, in_creds->second_ticket.length, &second_ticket_data, &len); if(ret) return ret; second_ticket = &second_ticket_data; } if (impersonate_principal) { krb5_crypto crypto; PA_S4U2Self self; krb5_data data; void *buf; size_t size; self.name = impersonate_principal->name; self.realm = impersonate_principal->realm; self.auth = estrdup("Kerberos"); ret = _krb5_s4u2self_to_checksumdata(context, &self, &data); if (ret) { free(self.auth); goto out; } ret = krb5_crypto_init(context, &krbtgt->session, 0, &crypto); if (ret) { free(self.auth); krb5_data_free(&data); goto out; } ret = krb5_create_checksum(context, crypto, KRB5_KU_OTHER_CKSUM, 0, data.data, data.length, &self.cksum); krb5_crypto_destroy(context, crypto); krb5_data_free(&data); if (ret) { free(self.auth); goto out; } ASN1_MALLOC_ENCODE(PA_S4U2Self, buf, len, &self, &size, ret); free(self.auth); free_Checksum(&self.cksum); if (ret) goto out; if (len != size) krb5_abortx(context, "internal asn1 error"); ret = krb5_padata_add(context, &padata, KRB5_PADATA_FOR_USER, buf, len); if (ret) goto out; } ret = init_tgs_req (context, id, addresses, flags, second_ticket, in_creds, krbtgt, nonce, &padata, &subkey, &req); if (ret) goto out; ASN1_MALLOC_ENCODE(TGS_REQ, enc.data, enc.length, &req, &len, ret); if (ret) goto out; if(enc.length != len) krb5_abortx(context, "internal error in ASN.1 encoder"); /* don't free addresses */ req.req_body.addresses = NULL; free_TGS_REQ(&req); /* * Send and receive */ { krb5_sendto_ctx stctx; ret = krb5_sendto_ctx_alloc(context, &stctx); if (ret) return ret; krb5_sendto_ctx_set_func(stctx, _krb5_kdc_retry, NULL); ret = krb5_sendto_context (context, stctx, &enc, krbtgt->server->name.name_string.val[1], &resp); krb5_sendto_ctx_free(context, stctx); } if(ret) goto out; memset(&rep, 0, sizeof(rep)); if(decode_TGS_REP(resp.data, resp.length, &rep.kdc_rep, &len) == 0) { unsigned eflags = 0; ret = krb5_copy_principal(context, in_creds->client, &out_creds->client); if(ret) goto out2; ret = krb5_copy_principal(context, in_creds->server, &out_creds->server); if(ret) goto out2; /* this should go someplace else */ out_creds->times.endtime = in_creds->times.endtime; /* XXX should do better testing */ if (flags.b.constrained_delegation || impersonate_principal) eflags |= EXTRACT_TICKET_ALLOW_CNAME_MISMATCH; ret = _krb5_extract_ticket(context, &rep, out_creds, &krbtgt->session, NULL, 0, &krbtgt->addresses, nonce, eflags, decrypt_tkt_with_subkey, subkey); out2: krb5_free_kdc_rep(context, &rep); } else if(krb5_rd_error(context, &resp, &error) == 0) { ret = krb5_error_from_rd_error(context, &error, in_creds); krb5_free_error_contents(context, &error); } else if(resp.length > 0 && ((char*)resp.data)[0] == 4) { ret = KRB5KRB_AP_ERR_V4_REPLY; krb5_clear_error_message(context); } else { ret = KRB5KRB_AP_ERR_MSG_TYPE; krb5_clear_error_message(context); } out: if (second_ticket == &second_ticket_data) free_Ticket(&second_ticket_data); free_METHOD_DATA(&padata); krb5_data_free(&resp); krb5_data_free(&enc); if(subkey) krb5_free_keyblock(context, subkey); return ret; }
krb5_error_code _krb5_init_tgs_req(krb5_context context, krb5_ccache ccache, krb5_addresses *addresses, krb5_kdc_flags flags, krb5_const_principal impersonate_principal, Ticket *second_ticket, krb5_creds *in_creds, krb5_creds *krbtgt, unsigned nonce, METHOD_DATA *padata, krb5_keyblock **subkey, TGS_REQ *t) { krb5_auth_context ac = NULL; krb5_error_code ret = 0; /* inherit the forwardable/proxyable flags from the krbtgt */ flags.b.forwardable = krbtgt->flags.b.forwardable; flags.b.proxiable = krbtgt->flags.b.proxiable; if (ccache->ops->tgt_req) { KERB_TGS_REQ_OUT out; KERB_TGS_REQ_IN in; memset(&in, 0, sizeof(in)); memset(&out, 0, sizeof(out)); ret = ccache->ops->tgt_req(context, ccache, &in, &out); if (ret) return ret; free_KERB_TGS_REQ_OUT(&out); return 0; } memset(t, 0, sizeof(*t)); if (impersonate_principal) { krb5_crypto crypto; PA_S4U2Self self; krb5_data data; void *buf; size_t size, len; self.name = impersonate_principal->name; self.realm = impersonate_principal->realm; self.auth = rk_UNCONST("Kerberos"); ret = _krb5_s4u2self_to_checksumdata(context, &self, &data); if (ret) goto fail; ret = krb5_crypto_init(context, &krbtgt->session, 0, &crypto); if (ret) { krb5_data_free(&data); goto fail; } ret = krb5_create_checksum(context, crypto, KRB5_KU_OTHER_CKSUM, 0, data.data, data.length, &self.cksum); krb5_crypto_destroy(context, crypto); krb5_data_free(&data); if (ret) goto fail; ASN1_MALLOC_ENCODE(PA_S4U2Self, buf, len, &self, &size, ret); free_Checksum(&self.cksum); if (ret) goto fail; if (len != size) krb5_abortx(context, "internal asn1 error"); ret = krb5_padata_add(context, padata, KRB5_PADATA_FOR_USER, buf, len); if (ret) goto fail; } t->pvno = 5; t->msg_type = krb_tgs_req; if (in_creds->session.keytype) { ALLOC_SEQ(&t->req_body.etype, 1); if(t->req_body.etype.val == NULL) { ret = ENOMEM; krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); goto fail; } t->req_body.etype.val[0] = in_creds->session.keytype; } else { ret = _krb5_init_etype(context, KRB5_PDU_TGS_REQUEST, &t->req_body.etype.len, &t->req_body.etype.val, NULL); } if (ret) goto fail; t->req_body.addresses = addresses; t->req_body.kdc_options = flags.b; ret = copy_Realm(&in_creds->server->realm, &t->req_body.realm); if (ret) goto fail; ALLOC(t->req_body.sname, 1); if (t->req_body.sname == NULL) { ret = ENOMEM; krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); goto fail; } /* some versions of some code might require that the client be present in TGS-REQs, but this is clearly against the spec */ ret = copy_PrincipalName(&in_creds->server->name, t->req_body.sname); if (ret) goto fail; /* req_body.till should be NULL if there is no endtime specified, but old MIT code (like DCE secd) doesn't like that */ ALLOC(t->req_body.till, 1); if(t->req_body.till == NULL){ ret = ENOMEM; krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); goto fail; } *t->req_body.till = in_creds->times.endtime; t->req_body.nonce = nonce; if(second_ticket){ ALLOC(t->req_body.additional_tickets, 1); if (t->req_body.additional_tickets == NULL) { ret = ENOMEM; krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); goto fail; } ALLOC_SEQ(t->req_body.additional_tickets, 1); if (t->req_body.additional_tickets->val == NULL) { ret = ENOMEM; krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); goto fail; } ret = copy_Ticket(second_ticket, t->req_body.additional_tickets->val); if (ret) goto fail; } ALLOC(t->padata, 1); if (t->padata == NULL) { ret = ENOMEM; krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); goto fail; } ALLOC_SEQ(t->padata, 1 + padata->len); if (t->padata->val == NULL) { ret = ENOMEM; krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); goto fail; } { size_t i; for (i = 0; i < padata->len; i++) { ret = copy_PA_DATA(&padata->val[i], &t->padata->val[i + 1]); if (ret) { krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); goto fail; } } } ret = krb5_auth_con_init(context, &ac); if(ret) goto fail; ret = krb5_auth_con_generatelocalsubkey(context, ac, &krbtgt->session); if (ret) goto fail; ret = set_auth_data (context, &t->req_body, &in_creds->authdata, ac->local_subkey); if (ret) goto fail; ret = make_pa_tgs_req(context, ac, &t->req_body, &t->padata->val[0], ccache, krbtgt); if(ret) goto fail; ret = krb5_auth_con_getlocalsubkey(context, ac, subkey); if (ret) goto fail; fail: if (ac) krb5_auth_con_free(context, ac); if (ret) { t->req_body.addresses = NULL; free_TGS_REQ (t); } return ret; }