diff --git a/kdc/kerberos5.c b/kdc/kerberos5.c index 5d5ad5a5c..69a62ecb4 100644 --- a/kdc/kerberos5.c +++ b/kdc/kerberos5.c @@ -1820,6 +1820,7 @@ generate_pac(astgs_request_t r, Key *skey) krb5_error_code ret; krb5_pac p = NULL; krb5_data data; + uint16_t rodc_id; ret = _kdc_pac_generate(r->context, r->client, &p); if (ret) { @@ -1830,10 +1831,13 @@ generate_pac(astgs_request_t r, Key *skey) if (p == NULL) return 0; + rodc_id = r->server->entry.kvno >> 16; + ret = _krb5_pac_sign(r->context, p, r->et.authtime, r->client->entry.principal, &skey->key, /* Server key */ &skey->key, /* FIXME: should be krbtgt key */ + rodc_id, &data); krb5_pac_free(r->context, p); if (ret) { @@ -1842,9 +1846,7 @@ generate_pac(astgs_request_t r, Key *skey) return ret; } - ret = _kdc_tkt_add_if_relevant_ad(r->context, &r->et, - KRB5_AUTHDATA_WIN2K_PAC, - &data); + ret = _kdc_tkt_insert_pac(r->context, &r->et, &data); krb5_data_free(&data); return ret; @@ -2607,64 +2609,3 @@ out: krb5_free_keyblock_contents(r->context, &r->session_key); return ret; } - -/* - * Add the AuthorizationData `data´ of `type´ to the last element in - * the sequence of authorization_data in `tkt´ wrapped in an IF_RELEVANT - */ - -krb5_error_code -_kdc_tkt_add_if_relevant_ad(krb5_context context, - EncTicketPart *tkt, - int type, - const krb5_data *data) -{ - krb5_error_code ret; - size_t size = 0; - - if (tkt->authorization_data == NULL) { - tkt->authorization_data = calloc(1, sizeof(*tkt->authorization_data)); - if (tkt->authorization_data == NULL) { - krb5_set_error_message(context, ENOMEM, "out of memory"); - return ENOMEM; - } - } - - /* add the entry to the last element */ - { - AuthorizationData ad = { 0, NULL }; - AuthorizationDataElement ade; - - ade.ad_type = type; - ade.ad_data = *data; - - ret = add_AuthorizationData(&ad, &ade); - if (ret) { - krb5_set_error_message(context, ret, "add AuthorizationData failed"); - return ret; - } - - ade.ad_type = KRB5_AUTHDATA_IF_RELEVANT; - - ASN1_MALLOC_ENCODE(AuthorizationData, - ade.ad_data.data, ade.ad_data.length, - &ad, &size, ret); - free_AuthorizationData(&ad); - if (ret) { - krb5_set_error_message(context, ret, "ASN.1 encode of " - "AuthorizationData failed"); - return ret; - } - if (ade.ad_data.length != size) - krb5_abortx(context, "internal asn.1 encoder error"); - - ret = add_AuthorizationData(tkt->authorization_data, &ade); - der_free_octet_string(&ade.ad_data); - if (ret) { - krb5_set_error_message(context, ret, "add AuthorizationData failed"); - return ret; - } - } - - return 0; -} diff --git a/kdc/krb5tgs.c b/kdc/krb5tgs.c index 2c3dd46e8..2f79be192 100644 --- a/kdc/krb5tgs.c +++ b/kdc/krb5tgs.c @@ -59,85 +59,64 @@ check_PAC(krb5_context context, hdb_entry_ex *client, hdb_entry_ex *server, hdb_entry_ex *krbtgt, + hdb_entry_ex *ticket_server, const EncryptionKey *server_check_key, - const EncryptionKey *server_sign_key, - const EncryptionKey *krbtgt_sign_key, + const EncryptionKey *krbtgt_check_key, EncTicketPart *tkt, - krb5_data *rspac, - int *signedpath) + krb5_boolean *kdc_issued, + krb5_pac *ppac) { - AuthorizationData *ad = tkt->authorization_data; - unsigned i, j; + krb5_pac pac = NULL; krb5_error_code ret; + krb5_boolean signedticket; - if (ad == NULL || ad->len == 0) - return 0; + *kdc_issued = FALSE; + *ppac = NULL; - for (i = 0; i < ad->len; i++) { - AuthorizationData child; + ret = _krb5_kdc_pac_ticket_parse(context, tkt, &signedticket, &pac); + if (ret || pac == NULL) + return ret; - if (ad->val[i].ad_type != KRB5_AUTHDATA_IF_RELEVANT) - continue; + /* Verify the server signature. */ + ret = krb5_pac_verify(context, pac, tkt->authtime, client_principal, + server_check_key, NULL); + if (ret) { + krb5_pac_free(context, pac); + return ret; + } - ret = decode_AuthorizationData(ad->val[i].ad_data.data, - ad->val[i].ad_data.length, - &child, - NULL); - if (ret) { - krb5_set_error_message(context, ret, "Failed to decode " - "IF_RELEVANT with %d", ret); - return ret; - } - for (j = 0; j < child.len; j++) { - - if (child.val[j].ad_type == KRB5_AUTHDATA_WIN2K_PAC) { - int signed_pac = 0; - krb5_pac pac; - - /* Found PAC */ - ret = krb5_pac_parse(context, - child.val[j].ad_data.data, - child.val[j].ad_data.length, - &pac); - free_AuthorizationData(&child); - if (ret) - return ret; - - ret = krb5_pac_verify(context, pac, tkt->authtime, - client_principal, - server_check_key, NULL); - if (ret) { - krb5_pac_free(context, pac); - return ret; - } - - ret = _kdc_pac_verify(context, client_principal, - delegated_proxy_principal, - client, server, krbtgt, &pac, &signed_pac); - if (ret) { - krb5_pac_free(context, pac); - return ret; - } - - /* - * Only re-sign PAC if we could verify it with the PAC - * function. The no-verify case happens when we get in - * a PAC from cross realm from a Windows domain and - * that there is no PAC verification function. - */ - if (signed_pac) { - *signedpath = 1; - ret = _krb5_pac_sign(context, pac, tkt->authtime, - client_principal, - server_sign_key, krbtgt_sign_key, rspac); - } + /* Verify the KDC signatures. */ + ret = _kdc_pac_verify(context, client_principal, delegated_proxy_principal, + client, server, krbtgt, &pac); + if (ret == KRB5_PLUGIN_NO_HANDLE) { + /* + * We can't verify the KDC signatures if the ticket was issued by + * another realm's KDC. + */ + if (krb5_realm_compare(context, server->entry.principal, + ticket_server->entry.principal)) { + ret = krb5_pac_verify(context, pac, 0, NULL, NULL, + krbtgt_check_key); + if (ret) { krb5_pac_free(context, pac); - return ret; } } - free_AuthorizationData(&child); + /* Discard the PAC if the plugin didn't handle it */ + krb5_pac_free(context, pac); + ret = krb5_pac_init(context, &pac); + if (ret) + return ret; + } else if (ret) { + krb5_pac_free(context, pac); + return ret; } + + *kdc_issued = signedticket || + krb5_principal_is_krbtgt(context, + ticket_server->entry.principal); + *ppac = pac; + return 0; } @@ -533,11 +512,12 @@ fix_transited_encoding(krb5_context context, static krb5_error_code tgs_make_reply(astgs_request_t r, - krb5_const_principal tgt_name, + krb5_principal tgt_name, const EncTicketPart *tgt, const krb5_keyblock *replykey, int rk_is_subkey, const EncryptionKey *serverkey, + const EncryptionKey *krbtgtkey, const krb5_keyblock *sessionkey, krb5_kvno kvno, AuthorizationData *auth_data, @@ -546,9 +526,9 @@ tgs_make_reply(astgs_request_t r, hdb_entry_ex *client, krb5_principal client_principal, const char *tgt_realm, - hdb_entry_ex *krbtgt, - krb5_enctype krbtgt_etype, - const krb5_data *rspac, + krb5_pac mspac, + uint16_t rodc_id, + krb5_boolean add_ticket_sig, const METHOD_DATA *enc_pa_data) { krb5_context context = r->context; @@ -692,23 +672,6 @@ tgs_make_reply(astgs_request_t r, if (!server->entry.flags.proxiable) et.flags.proxiable = 0; - /* - * For anonymous tickets, we should filter out positive authorization data - * that could reveal the client's identity, and return a policy error for - * restrictive authorization data. Policy for unknown authorization types - * is implementation dependent. - */ - if (rspac->length && !et.flags.anonymous) { - /* - * No not need to filter out the any PAC from the - * auth_data since it's signed by the KDC. - */ - ret = _kdc_tkt_add_if_relevant_ad(context, &et, - KRB5_AUTHDATA_WIN2K_PAC, rspac); - if (ret) - goto out; - } - if (auth_data) { unsigned int i = 0; @@ -775,6 +738,20 @@ tgs_make_reply(astgs_request_t r, is_weak = 1; } + /* + * For anonymous tickets, we should filter out positive authorization data + * that could reveal the client's identity, and return a policy error for + * restrictive authorization data. Policy for unknown authorization types + * is implementation dependent. + */ + if (mspac && !et.flags.anonymous) { + + /* The PAC should be the last change to the ticket. */ + ret = _krb5_kdc_pac_sign_ticket(context, mspac, tgt_name, serverkey, + krbtgtkey, rodc_id, add_ticket_sig, &et); + if (ret) + goto out; + } /* It is somewhat unclear where the etype in the following encryption should come from. What we have is a session @@ -925,6 +902,7 @@ tgs_parse_request(astgs_request_t r, int **cusec, AuthorizationData **auth_data, krb5_keyblock **replykey, + Key **header_key, int *rk_is_subkey) { krb5_context context = r->context; @@ -1094,6 +1072,8 @@ next_kvno: goto out; } + *header_key = tkey; + { krb5_authenticator auth; @@ -1283,10 +1263,62 @@ eout: return ENOMEM; } +static krb5_error_code +db_fetch_client(krb5_context context, + krb5_kdc_configuration *config, + int flags, + krb5_principal cp, + const char *cpn, + const char *krbtgt_realm, + HDB **clientdb, + hdb_entry_ex **client_out) +{ + krb5_error_code ret; + hdb_entry_ex *client = NULL; + + *client_out = NULL; + + ret = _kdc_db_fetch(context, config, cp, HDB_F_GET_CLIENT | flags, + NULL, clientdb, &client); + if (ret == HDB_ERR_NOT_FOUND_HERE) { + /* + * This is OK, we are just trying to find out if they have + * been disabled or deleted in the meantime; missing secrets + * are OK. + */ + } else if (ret) { + /* + * If the client belongs to the same realm as our TGS, it + * should exist in the local database. + */ + const char *msg; + + 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, 4, "Client no longer in database: %s", cpn); + return ret; + } + + msg = krb5_get_error_message(context, ret); + kdc_log(context, config, 4, "Client not found in database: %s", msg); + krb5_free_error_message(context, msg); + } else if (client->entry.flags.invalid || !client->entry.flags.client) { + kdc_log(context, config, 4, "Client has invalid bit set"); + _kdc_free_ent(context, client); + return KRB5KDC_ERR_POLICY; + } + + *client_out = client; + + return 0; +} + static krb5_error_code tgs_build_reply(astgs_request_t priv, hdb_entry_ex *krbtgt, krb5_enctype krbtgt_etype, + Key *tkey_check, const krb5_keyblock *replykey, int rk_is_subkey, krb5_ticket *ticket, @@ -1310,7 +1342,9 @@ tgs_build_reply(astgs_request_t priv, const EncryptionKey *ekey; krb5_keyblock sessionkey; krb5_kvno kvno; - krb5_data rspac; + krb5_pac mspac = NULL; + uint16_t rodc_id; + krb5_boolean add_ticket_sig = FALSE; const char *tgt_realm = /* Realm of TGT issuer */ krb5_principal_get_realm(context, krbtgt->entry.principal); const char *our_realm = /* Realm of this KDC */ @@ -1326,15 +1360,13 @@ tgs_build_reply(astgs_request_t priv, Realm r; EncTicketPart adtkt; char opt_str[128]; - int signedpath = 0; + krb5_boolean kdc_issued = FALSE; - Key *tkey_check; Key *tkey_sign; int flags = HDB_F_FOR_TGS_REQ; memset(&sessionkey, 0, sizeof(sessionkey)); memset(&adtkt, 0, sizeof(adtkt)); - krb5_data_zero(&rspac); memset(&enc_pa_data, 0, sizeof(enc_pa_data)); s = b->sname; @@ -1640,20 +1672,6 @@ server_lookup: * backward. */ - /* - * Validate authorization data - */ - - ret = hdb_enctype2key(context, &krbtgt->entry, NULL, /* XXX use the right kvno! */ - krbtgt_etype, &tkey_check); - if(ret) { - kdc_log(context, config, 4, - "Failed to find key for krbtgt PAC check"); - _kdc_audit_addreason((kdc_request_t)priv, - "No key for krbtgt PAC check"); - goto out; - } - /* * Now refetch the primary krbtgt, and get the current kvno (the * sign check may have been on an old kvno, and the server may @@ -1753,51 +1771,15 @@ server_lookup: flags |= HDB_F_SYNTHETIC_OK; } } - ret = _kdc_db_fetch(context, config, cp, HDB_F_GET_CLIENT | flags, - NULL, &clientdb, &client); + ret = db_fetch_client(context, config, flags, cp, cpn, our_realm, + &clientdb, &client); + if (ret) + goto out; flags &= ~HDB_F_SYNTHETIC_OK; priv->client = client; - if(ret == HDB_ERR_NOT_FOUND_HERE) { - /* This is OK, we are just trying to find out if they have - * been disabled or deleted in the meantime, missing secrets - * is OK */ - } else if(ret){ - const char *krbtgt_realm, *msg; - /* - * If the client belongs to the same realm as our krbtgt, it - * should exist in the local database. - * - */ - - krbtgt_realm = krb5_principal_get_realm(context, krbtgt_out->entry.principal); - - 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, 4, "Client no longer in database: %s", - cpn); - _kdc_audit_addreason((kdc_request_t)priv, "Client no longer in HDB"); - goto out; - } - - msg = krb5_get_error_message(context, ret); - kdc_log(context, config, 4, "Client not found in database: %s", msg); - _kdc_audit_addreason((kdc_request_t)priv, "Client does not exist"); - krb5_free_error_message(context, msg); - } else if (ret == 0 && - (client->entry.flags.invalid || !client->entry.flags.client)) { - _kdc_audit_addreason((kdc_request_t)priv, "Client has invalid bit set"); - kdc_log(context, config, 4, "Client has invalid bit set"); - ret = KRB5KDC_ERR_POLICY; - goto out; - } - - ret = check_PAC(context, config, cp, NULL, - client, server, krbtgt, - &tkey_check->key, - ekey, &tkey_sign->key, - tgt, &rspac, &signedpath); + ret = check_PAC(context, config, cp, NULL, client, server, krbtgt, krbtgt, + &tkey_check->key, &tkey_check->key, tgt, &kdc_issued, &mspac); if (ret) { const char *msg = krb5_get_error_message(context, ret); _kdc_audit_addreason((kdc_request_t)priv, "PAC check failed"); @@ -1957,29 +1939,15 @@ server_lookup: goto out; /* kdc_check_flags() calls _kdc_audit_addreason() */ /* If we were about to put a PAC into the ticket, we better fix it to be the right PAC */ - if(rspac.data) { - krb5_pac p = NULL; - krb5_data_free(&rspac); - ret = _kdc_pac_generate(context, s4u2self_impersonated_client, &p); + if (mspac) { + krb5_pac_free(context, mspac); + mspac = NULL; + ret = _kdc_pac_generate(context, s4u2self_impersonated_client, &mspac); if (ret) { - _kdc_audit_addreason((kdc_request_t)priv, - "KRB5SignedPath missing"); kdc_log(context, config, 4, "PAC generation failed for -- %s", tpn); goto out; } - if (p != NULL) { - ret = _krb5_pac_sign(context, p, ticket->ticket.authtime, - s4u2self_impersonated_client->entry.principal, - ekey, &tkey_sign->key, - &rspac); - krb5_pac_free(context, p); - if (ret) { - kdc_log(context, config, 4, "PAC signing failed for -- %s", - tpn); - goto out; - } - } } /* @@ -2023,23 +1991,26 @@ server_lookup: && b->kdc_options.cname_in_addl_tkt && b->kdc_options.enc_tkt_in_skey == 0) { - int ad_signedpath = 0; + hdb_entry_ex *adclient = NULL; + krb5_boolean ad_kdc_issued = FALSE; Key *clientkey; Ticket *t; /* - * Require that the KDC have issued the service's krbtgt (not - * self-issued ticket with kimpersonate(1). + * We require that the service's krbtgt has a PAC. */ - if (!signedpath) { + if (mspac == NULL) { ret = KRB5KDC_ERR_BADOPTION; - _kdc_audit_addreason((kdc_request_t)priv, "KRB5SignedPath missing"); + _kdc_audit_addreason((kdc_request_t)priv, "Missing PAC"); kdc_log(context, config, 4, - "Constrained delegation done on service ticket %s/%s", + "Constrained delegation without PAC, %s/%s", cpn, spn); goto out; } + krb5_pac_free(context, mspac); + mspac = NULL; + t = &b->additional_tickets->val[0]; ret = hdb_enctype2key(context, &client->entry, @@ -2115,19 +2086,34 @@ server_lookup: goto out; } - krb5_data_free(&rspac); + /* Try lookup the delegated client in DB */ + ret = db_fetch_client(context, config, flags, tp, tpn, our_realm, + NULL, &adclient); + if (ret) + goto out; + + if (adclient != NULL) { + struct astgs_request_desc deleg_req; + + deleg_req = *priv; + deleg_req.client = adclient; + deleg_req.client_princ = tp; + + ret = kdc_check_flags(&deleg_req, FALSE); + if (ret) { + _kdc_free_ent(context, adclient); + goto out; + } + } /* - * generate the PAC for the user. - * * TODO: pass in t->sname and t->realm and build * a S4U_DELEGATION_INFO blob to the PAC. */ - ret = check_PAC(context, config, tp, dp, - client, server, krbtgt, - &clientkey->key, - ekey, &tkey_sign->key, - &adtkt, &rspac, &ad_signedpath); + ret = check_PAC(context, config, tp, dp, adclient, server, krbtgt, client, + &clientkey->key, &tkey_check->key, &adtkt, &ad_kdc_issued, &mspac); + if (adclient) + _kdc_free_ent(context, adclient); if (ret) { const char *msg = krb5_get_error_message(context, ret); _kdc_audit_addreason((kdc_request_t)priv, @@ -2140,13 +2126,12 @@ server_lookup: goto out; } - if (!ad_signedpath) { + if (mspac == NULL || !ad_kdc_issued) { ret = KRB5KDC_ERR_BADOPTION; kdc_log(context, config, 4, - "Ticket not signed with PAC nor SignedPath service %s failed " - "for delegation to %s for client %s (%s)" - "from %s", - spn, tpn, dpn, cpn, from); + "Ticket not signed with PAC; service %s failed for " + "for delegation to %s for client %s (%s) from %s; (%s).", + spn, tpn, dpn, cpn, from, mspac ? "Ticket unsigned" : "No PAC"); _kdc_audit_addreason((kdc_request_t)priv, "Constrained delegation ticket not signed"); goto out; @@ -2229,6 +2214,25 @@ server_lookup: } } + /* + * Only add ticket signature if the requested server is not krbtgt, and + * either the header server is krbtgt or, in the case of renewal/validation + * if it was signed with PAC ticket signature and we verified it. + * Currently Heimdal only allows renewal of krbtgt anyway but that might + * change one day (see issue #763) so make sure to check for it. + */ + + if (kdc_issued && + !krb5_principal_is_krbtgt(context, server->entry.principal)) + add_ticket_sig = TRUE; + + /* + * Active-Directory implementations use the high part of the kvno as the + * read-only-dc identifier, we need to embed it in the PAC KDC signatures. + */ + + rodc_id = krbtgt_out->entry.kvno >> 16; + /* * */ @@ -2239,6 +2243,7 @@ server_lookup: replykey, rk_is_subkey, ekey, + &tkey_sign->key, &sessionkey, kvno, *auth_data, @@ -2247,9 +2252,9 @@ server_lookup: client, cp, tgt_realm, - krbtgt_out, - tkey_sign->key.keytype, - &rspac, + mspac, + rodc_id, + add_ticket_sig, &enc_pa_data); out: @@ -2259,7 +2264,6 @@ out: free(krbtgt_out_n); _krb5_free_capath(context, capath); - krb5_data_free(&rspac); krb5_free_keyblock_contents(context, &sessionkey); if(krbtgt_out) _kdc_free_ent(context, krbtgt_out); @@ -2281,6 +2285,9 @@ out: free_EncTicketPart(&adtkt); + if (mspac) + krb5_pac_free(context, mspac); + return ret; } @@ -2302,6 +2309,7 @@ _kdc_tgs_rep(astgs_request_t r) krb5_error_code ret; int i = 0; const PA_DATA *tgs_req; + Key *header_key; hdb_entry_ex *krbtgt = NULL; krb5_ticket *ticket = NULL; @@ -2338,6 +2346,7 @@ _kdc_tgs_rep(astgs_request_t r) &csec, &cusec, &auth_data, &replykey, + &header_key, &rk_is_subkey); if (ret == HDB_ERR_NOT_FOUND_HERE) { /* kdc_log() is called in tgs_parse_request() */ @@ -2359,6 +2368,7 @@ _kdc_tgs_rep(astgs_request_t r) ret = tgs_build_reply(r, krbtgt, krbtgt_etype, + header_key, replykey, rk_is_subkey, ticket, diff --git a/kdc/windc.c b/kdc/windc.c index 21e0839aa..18dfb9556 100644 --- a/kdc/windc.c +++ b/kdc/windc.c @@ -91,17 +91,32 @@ _kdc_pac_generate(krb5_context context, hdb_entry_ex *client, krb5_pac *pac) { + krb5_error_code ret = 0; struct generate_uc uc; - if (!have_plugin) + *pac = NULL; + + if (krb5_config_get_bool_default(context, NULL, FALSE, "realms", + client->entry.principal->realm, + "disable_pac", NULL)) return 0; - uc.client = client; - uc.pac = pac; + if (have_plugin) { - (void)_krb5_plugin_run_f(context, &windc_plugin_data, - 0, &uc, generate); - return 0; + uc.client = client; + uc.pac = pac; + + ret = _krb5_plugin_run_f(context, &windc_plugin_data, + 0, &uc, generate); + if (ret != KRB5_PLUGIN_NO_HANDLE) + return ret; + ret = 0; + } + + if (*pac == NULL) + ret = krb5_pac_init(context, pac); + + return ret; } struct verify_uc { @@ -111,26 +126,23 @@ struct verify_uc { hdb_entry_ex *server; hdb_entry_ex *krbtgt; krb5_pac *pac; - int *verified; }; static krb5_error_code KRB5_LIB_CALL verify(krb5_context context, const void *plug, void *plugctx, void *userctx) { krb5plugin_windc_ftable *ft = (krb5plugin_windc_ftable *)plug; - struct verify_uc *uc = (struct verify_uc *)userctx; + struct verify_uc *uc = (struct verify_uc *)userctx; krb5_error_code ret; if (ft->pac_verify == NULL) return KRB5_PLUGIN_NO_HANDLE; + ret = ft->pac_verify((void *)plug, context, uc->client_principal, uc->delegated_proxy_principal, uc->client, uc->server, uc->krbtgt, uc->pac); - if (ret == 0) - (*uc->verified) = 1; - - return 0; + return ret; } krb5_error_code @@ -140,13 +152,12 @@ _kdc_pac_verify(krb5_context context, hdb_entry_ex *client, hdb_entry_ex *server, hdb_entry_ex *krbtgt, - krb5_pac *pac, - int *verified) + krb5_pac *pac) { struct verify_uc uc; if (!have_plugin) - return 0; + return KRB5_PLUGIN_NO_HANDLE; uc.client_principal = client_principal; uc.delegated_proxy_principal = delegated_proxy_principal; @@ -154,11 +165,9 @@ _kdc_pac_verify(krb5_context context, uc.server = server; uc.krbtgt = krbtgt; uc.pac = pac; - uc.verified = verified; - (void)_krb5_plugin_run_f(context, &windc_plugin_data, + return _krb5_plugin_run_f(context, &windc_plugin_data, 0, &uc, verify); - return 0; } struct check_uc { diff --git a/kdc/windc_plugin.h b/kdc/windc_plugin.h index 4401500e6..e63ece854 100644 --- a/kdc/windc_plugin.h +++ b/kdc/windc_plugin.h @@ -43,8 +43,9 @@ * krb5_pac_init and fill in the PAC structure for the principal using * krb5_pac_add_buffer. * - * The PAC verify function should verify all components in the PAC - * using krb5_pac_get_types and krb5_pac_get_buffer for all types. + * The PAC verify function should verify the PAC KDC signatures by fetching + * the right KDC key and calling krb5_pac_verify() with that KDC key. + * Optionally, update the PAC buffers upon success. * * Check client access function check if the client is authorized. */ diff --git a/lib/krb5/Makefile.am b/lib/krb5/Makefile.am index 506155ee3..963c0b1fb 100644 --- a/lib/krb5/Makefile.am +++ b/lib/krb5/Makefile.am @@ -121,6 +121,7 @@ dist_libkrb5_la_SOURCES = \ appdefault.c \ asn1_glue.c \ auth_context.c \ + authdata.c \ build_ap_req.c \ build_auth.c \ cache.c \ diff --git a/lib/krb5/NTMakefile b/lib/krb5/NTMakefile index d3eca00b6..d32025130 100644 --- a/lib/krb5/NTMakefile +++ b/lib/krb5/NTMakefile @@ -42,6 +42,7 @@ libkrb5_OBJS = \ $(OBJ)\appdefault.obj \ $(OBJ)\asn1_glue.obj \ $(OBJ)\auth_context.obj \ + $(OBJ)\authdata.obj \ $(OBJ)\build_ap_req.obj \ $(OBJ)\build_auth.obj \ $(OBJ)\cache.obj \ @@ -204,6 +205,7 @@ dist_libkrb5_la_SOURCES = \ appdefault.c \ asn1_glue.c \ auth_context.c \ + authdata.c \ build_ap_req.c \ build_auth.c \ cache.c \ diff --git a/lib/krb5/authdata.c b/lib/krb5/authdata.c new file mode 100644 index 000000000..ac426618f --- /dev/null +++ b/lib/krb5/authdata.c @@ -0,0 +1,124 @@ +/* + * Copyright (c) 1997-2021 Kungliga Tekniska Högskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * Copyright (c) 2021 Isaac Boukris + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "krb5_locl.h" + +/* + * Add the AuthorizationData `data´ of `type´ to the last element in + * the sequence of authorization_data in `tkt´ wrapped in an IF_RELEVANT + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +_kdc_tkt_add_if_relevant_ad(krb5_context context, + EncTicketPart *tkt, + int type, + const krb5_data *data) +{ + krb5_error_code ret; + size_t size = 0; + + if (tkt->authorization_data == NULL) { + tkt->authorization_data = calloc(1, sizeof(*tkt->authorization_data)); + if (tkt->authorization_data == NULL) { + return krb5_enomem(context); + } + } + + /* add the entry to the last element */ + { + AuthorizationData ad = { 0, NULL }; + AuthorizationDataElement ade; + + ade.ad_type = type; + ade.ad_data = *data; + + ret = add_AuthorizationData(&ad, &ade); + if (ret) { + krb5_set_error_message(context, ret, "add AuthorizationData failed"); + return ret; + } + + ade.ad_type = KRB5_AUTHDATA_IF_RELEVANT; + + ASN1_MALLOC_ENCODE(AuthorizationData, + ade.ad_data.data, ade.ad_data.length, + &ad, &size, ret); + free_AuthorizationData(&ad); + if (ret) { + krb5_set_error_message(context, ret, "ASN.1 encode of " + "AuthorizationData failed"); + return ret; + } + if (ade.ad_data.length != size) + krb5_abortx(context, "internal asn.1 encoder error"); + + ret = add_AuthorizationData(tkt->authorization_data, &ade); + der_free_octet_string(&ade.ad_data); + if (ret) { + krb5_set_error_message(context, ret, "add AuthorizationData failed"); + return ret; + } + } + + return 0; +} + +/* + * Insert a PAC wrapped in AD-IF-RELEVANT container as the first AD element, + * as some clients such as Windows may fail to parse it otherwise. + */ + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +_kdc_tkt_insert_pac(krb5_context context, + EncTicketPart *tkt, + const krb5_data *data) +{ + AuthorizationDataElement ade; + unsigned int i; + krb5_error_code ret; + + ret = _kdc_tkt_add_if_relevant_ad(context, tkt, KRB5_AUTHDATA_WIN2K_PAC, + data); + if (ret) + return ret; + + heim_assert(tkt->authorization_data->len != 0, "No authorization_data!"); + ade = tkt->authorization_data->val[tkt->authorization_data->len - 1]; + for (i = 0; i < tkt->authorization_data->len - 1; i++) { + tkt->authorization_data->val[i + 1] = tkt->authorization_data->val[i]; + } + tkt->authorization_data->val[0] = ade; + + return 0; +} diff --git a/lib/krb5/krb5.conf.5 b/lib/krb5/krb5.conf.5 index d4588f536..eaf629b25 100644 --- a/lib/krb5/krb5.conf.5 +++ b/lib/krb5/krb5.conf.5 @@ -663,6 +663,12 @@ If then PKINIT client will tell KDCs which trust anchors it trusts. Defaults to .Va true . +.It Li disable_pac = BOOL +If +.Va true +then the KDC will not sign tickets with PAC, which disables S4U2Proxy support. +Defaults to +.Va false . .El .It Li } .El diff --git a/lib/krb5/libkrb5-exports.def.in b/lib/krb5/libkrb5-exports.def.in index fe2856c14..0d1d24b30 100644 --- a/lib/krb5/libkrb5-exports.def.in +++ b/lib/krb5/libkrb5-exports.def.in @@ -815,6 +815,11 @@ EXPORTS _krb5_get_int _krb5_get_int64 _krb5_pac_sign + _krb5_kdc_pac_sign_ticket + _krb5_kdc_pac_ticket_parse + _krb5_pac_get_kdc_checksum_info + _kdc_tkt_insert_pac + _kdc_tkt_add_if_relevant_ad _krb5_parse_moduli _krb5_pk_kdf _krb5_pk_load_id diff --git a/lib/krb5/pac.c b/lib/krb5/pac.c index 9fc0e57ad..ad2c9e17e 100644 --- a/lib/krb5/pac.c +++ b/lib/krb5/pac.c @@ -53,6 +53,8 @@ struct krb5_pac_data { struct PAC_INFO_BUFFER *server_checksum; struct PAC_INFO_BUFFER *privsvr_checksum; struct PAC_INFO_BUFFER *logon_name; + struct PAC_INFO_BUFFER *ticket_checksum; + krb5_data ticket_sign_data; }; #define PAC_ALIGNMENT 8 @@ -64,6 +66,7 @@ struct krb5_pac_data { #define PAC_PRIVSVR_CHECKSUM 7 #define PAC_LOGON_NAME 10 #define PAC_CONSTRAINED_DELEGATION 11 +#define PAC_TICKET_CHECKSUM 16 #define CHECK(r,f,l) \ do { \ @@ -148,13 +151,13 @@ krb5_pac_parse(krb5_context context, const void *ptr, size_t len, CHECK(ret, krb5_ret_uint32(sp, &tmp2), out); if (tmp < 1) { ret = EINVAL; /* Too few buffers */ - krb5_set_error_message(context, ret, N_("PAC have too few buffer", "")); + krb5_set_error_message(context, ret, N_("PAC has too few buffers", "")); goto out; } if (tmp2 != 0) { ret = EINVAL; /* Wrong version */ krb5_set_error_message(context, ret, - N_("PAC have wrong version %d", ""), + N_("PAC has wrong version %d", ""), (int)tmp2); goto out; } @@ -197,7 +200,7 @@ krb5_pac_parse(krb5_context context, const void *ptr, size_t len, if (p->pac->buffers[i].offset_lo > len) { ret = EINVAL; krb5_set_error_message(context, ret, - N_("PAC offset off end", "")); + N_("PAC offset overflow", "")); goto out; } if (p->pac->buffers[i].offset_lo < header_end) { @@ -210,7 +213,7 @@ krb5_pac_parse(krb5_context context, const void *ptr, size_t len, } if (p->pac->buffers[i].buffersize > len - p->pac->buffers[i].offset_lo){ ret = EINVAL; - krb5_set_error_message(context, ret, N_("PAC length off end", "")); + krb5_set_error_message(context, ret, N_("PAC length overflow", "")); goto out; } @@ -219,7 +222,7 @@ krb5_pac_parse(krb5_context context, const void *ptr, size_t len, if (p->server_checksum) { ret = EINVAL; krb5_set_error_message(context, ret, - N_("PAC have two server checksums", "")); + N_("PAC has multiple server checksums", "")); goto out; } p->server_checksum = &p->pac->buffers[i]; @@ -227,7 +230,7 @@ krb5_pac_parse(krb5_context context, const void *ptr, size_t len, if (p->privsvr_checksum) { ret = EINVAL; krb5_set_error_message(context, ret, - N_("PAC have two KDC checksums", "")); + N_("PAC has multiple KDC checksums", "")); goto out; } p->privsvr_checksum = &p->pac->buffers[i]; @@ -235,10 +238,18 @@ krb5_pac_parse(krb5_context context, const void *ptr, size_t len, if (p->logon_name) { ret = EINVAL; krb5_set_error_message(context, ret, - N_("PAC have two logon names", "")); + N_("PAC has multiple logon names", "")); goto out; } p->logon_name = &p->pac->buffers[i]; + } else if (p->pac->buffers[i].type == PAC_TICKET_CHECKSUM) { + if (p->ticket_checksum) { + ret = EINVAL; + krb5_set_error_message(context, ret, + N_("PAC has multiple ticket checksums", "")); + goto out; + } + p->ticket_checksum = &p->pac->buffers[i]; } } @@ -431,6 +442,7 @@ KRB5_LIB_FUNCTION void KRB5_LIB_CALL krb5_pac_free(krb5_context context, krb5_pac pac) { krb5_data_free(&pac->data); + krb5_data_free(&pac->ticket_sign_data); free(pac->pac); free(pac); } @@ -450,6 +462,7 @@ verify_checksum(krb5_context context, uint32_t type; krb5_error_code ret; Checksum cksum; + size_t cksumsize; memset(&cksum, 0, sizeof(cksum)); @@ -462,8 +475,17 @@ verify_checksum(krb5_context context, CHECK(ret, krb5_ret_uint32(sp, &type), out); cksum.cksumtype = type; - cksum.checksum.length = - sig->buffersize - krb5_storage_seek(sp, 0, SEEK_CUR); + + ret = krb5_checksumsize(context, type, &cksumsize); + if (ret) + goto out; + + /* Allow for RODCIdentifier trailer, see MS-PAC 2.8 */ + if (cksumsize > (sig->buffersize - krb5_storage_seek(sp, 0, SEEK_CUR))) { + ret = EINVAL; + goto out; + } + cksum.checksum.length = cksumsize; cksum.checksum.data = malloc(cksum.checksum.length); if (cksum.checksum.data == NULL) { ret = krb5_enomem(context); @@ -819,7 +841,6 @@ out: return ret; } - /** * Verify the PAC. * @@ -859,25 +880,25 @@ krb5_pac_verify(krb5_context context, return EINVAL; } - ret = verify_logonname(context, - pac->logon_name, - &pac->data, - authtime, - principal); - if (ret) - return ret; + if (principal != NULL) { + ret = verify_logonname(context, pac->logon_name, &pac->data, authtime, + principal); + if (ret) + return ret; + } + + if (pac->server_checksum->buffersize < 4 || + pac->privsvr_checksum->buffersize < 4) + return EINVAL; /* * in the service case, clean out data option of the privsvr and * server checksum before checking the checksum. */ + if (server != NULL) { krb5_data *copy; - if (pac->server_checksum->buffersize < 4 || - pac->privsvr_checksum->buffersize < 4) - return EINVAL; - ret = krb5_copy_data(context, &pac->data, ©); if (ret) return ret; @@ -911,6 +932,20 @@ krb5_pac_verify(krb5_context context, privsvr); if (ret) return ret; + + if (pac->ticket_sign_data.length != 0) { + if (pac->ticket_checksum == NULL) { + krb5_set_error_message(context, EINVAL, + "PAC missing ticket checksum"); + return EINVAL; + } + + ret = verify_checksum(context, pac->ticket_checksum, &pac->data, + pac->ticket_sign_data.data, + pac->ticket_sign_data.length, privsvr); + if (ret) + return ret; + } } return 0; @@ -979,13 +1014,14 @@ _krb5_pac_sign(krb5_context context, krb5_principal principal, const krb5_keyblock *server_key, const krb5_keyblock *priv_key, + uint16_t rodc_id, krb5_data *data) { krb5_error_code ret; krb5_storage *sp = NULL, *spdata = NULL; uint32_t end; size_t server_size, priv_size; - uint32_t server_offset = 0, priv_offset = 0; + uint32_t server_offset = 0, priv_offset = 0, ticket_offset = 0; uint32_t server_cksumtype = 0, priv_cksumtype = 0; int num = 0; size_t i; @@ -999,9 +1035,9 @@ _krb5_pac_sign(krb5_context context, p->server_checksum = &p->pac->buffers[i]; } if (p->server_checksum != &p->pac->buffers[i]) { - ret = EINVAL; + ret = KRB5KDC_ERR_BADOPTION; krb5_set_error_message(context, ret, - N_("PAC have two server checksums", "")); + N_("PAC has multiple server checksums", "")); goto out; } } else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) { @@ -1009,9 +1045,9 @@ _krb5_pac_sign(krb5_context context, p->privsvr_checksum = &p->pac->buffers[i]; } if (p->privsvr_checksum != &p->pac->buffers[i]) { - ret = EINVAL; + ret = KRB5KDC_ERR_BADOPTION; krb5_set_error_message(context, ret, - N_("PAC have two KDC checksums", "")); + N_("PAC has multiple KDC checksums", "")); goto out; } } else if (p->pac->buffers[i].type == PAC_LOGON_NAME) { @@ -1019,9 +1055,19 @@ _krb5_pac_sign(krb5_context context, p->logon_name = &p->pac->buffers[i]; } if (p->logon_name != &p->pac->buffers[i]) { - ret = EINVAL; + ret = KRB5KDC_ERR_BADOPTION; krb5_set_error_message(context, ret, - N_("PAC have two logon names", "")); + N_("PAC has multiple logon names", "")); + goto out; + } + } else if (p->pac->buffers[i].type == PAC_TICKET_CHECKSUM) { + if (p->ticket_checksum == NULL) { + p->ticket_checksum = &p->pac->buffers[i]; + } + if (p->ticket_checksum != &p->pac->buffers[i]) { + ret = KRB5KDC_ERR_BADOPTION; + krb5_set_error_message(context, ret, + N_("PAC has multiple ticket checksums", "")); goto out; } } @@ -1033,6 +1079,8 @@ _krb5_pac_sign(krb5_context context, num++; if (p->privsvr_checksum == NULL) num++; + if (p->ticket_sign_data.length != 0 && p->ticket_checksum == NULL) + num++; if (num) { void *ptr; @@ -1058,6 +1106,11 @@ _krb5_pac_sign(krb5_context context, memset(p->privsvr_checksum, 0, sizeof(*p->privsvr_checksum)); p->privsvr_checksum->type = PAC_PRIVSVR_CHECKSUM; } + if (p->ticket_sign_data.length != 0 && p->ticket_checksum == NULL) { + p->ticket_checksum = &p->pac->buffers[p->pac->numbuffers++]; + memset(p->ticket_checksum, 0, sizeof(*p->privsvr_checksum)); + p->ticket_checksum->type = PAC_TICKET_CHECKSUM; + } } /* Calculate LOGON NAME */ @@ -1069,6 +1122,7 @@ _krb5_pac_sign(krb5_context context, ret = pac_checksum(context, server_key, &server_cksumtype, &server_size); if (ret) goto out; + ret = pac_checksum(context, priv_key, &priv_cksumtype, &priv_size); if (ret) goto out; @@ -1109,10 +1163,24 @@ _krb5_pac_sign(krb5_context context, priv_offset = end + 4; CHECK(ret, krb5_store_uint32(spdata, priv_cksumtype), out); CHECK(ret, fill_zeros(context, spdata, priv_size), out); + if (rodc_id != 0) { + len += sizeof(rodc_id); + CHECK(ret, fill_zeros(context, spdata, sizeof(rodc_id)), out); + } + } else if (p->ticket_sign_data.length != 0 && + p->pac->buffers[i].type == PAC_TICKET_CHECKSUM) { + len = priv_size + 4; + ticket_offset = end + 4; + CHECK(ret, krb5_store_uint32(spdata, priv_cksumtype), out); + CHECK(ret, fill_zeros(context, spdata, priv_size), out); + if (rodc_id != 0) { + len += sizeof(rodc_id); + CHECK(ret, krb5_store_uint16(spdata, rodc_id), out); + } } else if (p->pac->buffers[i].type == PAC_LOGON_NAME) { len = krb5_storage_write(spdata, logon.data, logon.length); if (logon.length != len) { - ret = EINVAL; + ret = KRB5KDC_ERR_BADOPTION; goto out; } } else { @@ -1170,6 +1238,16 @@ _krb5_pac_sign(krb5_context context, } /* sign */ + if (p->ticket_sign_data.length) { + ret = create_checksum(context, priv_key, priv_cksumtype, + p->ticket_sign_data.data, + p->ticket_sign_data.length, + (char *)d.data + ticket_offset, priv_size); + if (ret) { + krb5_data_free(&d); + goto out; + } + } ret = create_checksum(context, server_key, server_cksumtype, d.data, d.length, (char *)d.data + server_offset, server_size); @@ -1185,6 +1263,32 @@ _krb5_pac_sign(krb5_context context, goto out; } + if (rodc_id != 0) { + krb5_data rd; + krb5_storage *rs = krb5_storage_emem(); + if (rs == NULL) { + krb5_data_free(&d); + ret = krb5_enomem(context); + goto out; + } + krb5_storage_set_flags(rs, KRB5_STORAGE_BYTEORDER_LE); + ret = krb5_store_uint16(rs, rodc_id); + if (ret) { + krb5_storage_free(rs); + krb5_data_free(&d); + goto out; + } + ret = krb5_storage_to_data(rs, &rd); + krb5_storage_free(rs); + if (ret) { + krb5_data_free(&d); + goto out; + } + heim_assert(rd.length == sizeof(rodc_id), "invalid length"); + memcpy((char *)d.data + priv_offset + priv_size, rd.data, rd.length); + krb5_data_free(&rd); + } + /* done */ *data = d; @@ -1201,3 +1305,221 @@ out: krb5_storage_free(spdata); return ret; } + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +_krb5_pac_get_kdc_checksum_info(krb5_context context, + krb5_pac pac, + krb5_cksumtype *cstype, + uint16_t *rodc_id) +{ + krb5_error_code ret; + krb5_storage *sp = NULL; + const struct PAC_INFO_BUFFER *sig; + size_t cksumsize, prefix; + uint32_t type = 0; + + *cstype = 0; + *rodc_id = 0; + + sig = pac->privsvr_checksum; + if (sig == NULL) { + krb5_set_error_message(context, KRB5KDC_ERR_BADOPTION, + "PAC missing kdc checksum"); + return KRB5KDC_ERR_BADOPTION; + } + + sp = krb5_storage_from_mem((char *)pac->data.data + sig->offset_lo, + sig->buffersize); + if (sp == NULL) + return krb5_enomem(context); + + krb5_storage_set_flags(sp, KRB5_STORAGE_BYTEORDER_LE); + + ret = krb5_ret_uint32(sp, &type); + if (ret) + goto out; + + ret = krb5_checksumsize(context, type, &cksumsize); + if (ret) + goto out; + + prefix = krb5_storage_seek(sp, 0, SEEK_CUR); + + if ((sig->buffersize - prefix) >= cksumsize + 2) { + krb5_storage_seek(sp, cksumsize, SEEK_CUR); + ret = krb5_ret_uint16(sp, rodc_id); + if (ret) + goto out; + } + + *cstype = type; + +out: + krb5_storage_free(sp); + + return ret; +} + +static unsigned char single_zero = '\0'; +static krb5_data single_zero_pac = { 1, &single_zero }; + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +_krb5_kdc_pac_ticket_parse(krb5_context context, + EncTicketPart *tkt, + krb5_boolean *signedticket, + krb5_pac *ppac) +{ + AuthorizationData *ad = tkt->authorization_data; + krb5_boolean pac_found = FALSE; + krb5_pac pac = NULL; + unsigned i, j; + size_t len = 0; + krb5_error_code ret; + + *signedticket = FALSE; + *ppac = NULL; + + if (ad == NULL || ad->len == 0) + return 0; + + for (i = 0; i < ad->len; i++) { + AuthorizationData child; + + if (ad->val[i].ad_type == KRB5_AUTHDATA_WIN2K_PAC) + return KRB5KDC_ERR_BADOPTION; + + if (ad->val[i].ad_type != KRB5_AUTHDATA_IF_RELEVANT) + continue; + + ret = decode_AuthorizationData(ad->val[i].ad_data.data, + ad->val[i].ad_data.length, + &child, + NULL); + if (ret) { + krb5_set_error_message(context, ret, "Failed to decode " + "AD-IF-RELEVANT with %d", ret); + return ret; + } + + for (j = 0; j < child.len; j++) { + if (child.val[j].ad_type == KRB5_AUTHDATA_WIN2K_PAC) { + krb5_data adifr_data = ad->val[i].ad_data; + krb5_data pac_data = child.val[j].ad_data; + krb5_data recoded_adifr; + + if (pac_found) { + free_AuthorizationData(&child); + return KRB5KDC_ERR_BADOPTION; + } + pac_found = TRUE; + + ret = krb5_pac_parse(context, + pac_data.data, + pac_data.length, + &pac); + if (ret) { + free_AuthorizationData(&child); + return ret; + } + + if (pac->ticket_checksum == NULL) { + free_AuthorizationData(&child); + *ppac = pac; + continue; + } + + /* + * Encode the ticket with the PAC replaced with a single zero + * byte, to be used as input data to the ticket signature. + */ + + child.val[j].ad_data = single_zero_pac; + + ASN1_MALLOC_ENCODE(AuthorizationData, recoded_adifr.data, + recoded_adifr.length, &child, &len, ret); + if (recoded_adifr.length != len) + krb5_abortx(context, "Internal error in ASN.1 encoder"); + + child.val[j].ad_data = pac_data; + free_AuthorizationData(&child); + + if (ret) { + krb5_pac_free(context, pac); + return ret; + } + + ad->val[i].ad_data = recoded_adifr; + + ASN1_MALLOC_ENCODE(EncTicketPart, + pac->ticket_sign_data.data, + pac->ticket_sign_data.length, tkt, &len, + ret); + if(pac->ticket_sign_data.length != len) + krb5_abortx(context, "Internal error in ASN.1 encoder"); + + ad->val[i].ad_data = adifr_data; + krb5_data_free(&recoded_adifr); + + if (ret) { + krb5_pac_free(context, pac); + return ret; + } + + *signedticket = TRUE; + *ppac = pac; + } + } + free_AuthorizationData(&child); + } + return 0; +} + +KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL +_krb5_kdc_pac_sign_ticket(krb5_context context, + const krb5_pac pac, + krb5_principal client, + const krb5_keyblock *server_key, + const krb5_keyblock *kdc_key, + uint16_t rodc_id, + krb5_boolean add_ticket_sig, + EncTicketPart *tkt) +{ + krb5_error_code ret; + krb5_data tkt_data; + krb5_data rspac; + + krb5_data_zero(&rspac); + krb5_data_zero(&tkt_data); + + krb5_data_free(&pac->ticket_sign_data); + + if (add_ticket_sig) { + size_t len = 0; + + ret = _kdc_tkt_insert_pac(context, tkt, &single_zero_pac); + if (ret) + return ret; + + ASN1_MALLOC_ENCODE(EncTicketPart, tkt_data.data, tkt_data.length, + tkt, &len, ret); + if(tkt_data.length != len) + krb5_abortx(context, "Internal error in ASN.1 encoder"); + if (ret) + return ret; + + ret = remove_AuthorizationData(tkt->authorization_data, 0); + if (ret) { + krb5_data_free(&tkt_data); + return ret; + } + + pac->ticket_sign_data = tkt_data; + } + + ret = _krb5_pac_sign(context, pac, tkt->authtime, client, server_key, + kdc_key, rodc_id, &rspac); + if (ret) + return ret; + + return _kdc_tkt_insert_pac(context, tkt, &rspac); +} diff --git a/lib/krb5/test_pac.c b/lib/krb5/test_pac.c index 983294ecf..cf786c507 100644 --- a/lib/krb5/test_pac.c +++ b/lib/krb5/test_pac.c @@ -188,7 +188,7 @@ main(int argc, char **argv) krb5_err(context, 1, ret, "krb5_pac_verify"); ret = _krb5_pac_sign(context, pac, authtime, p, - &member_keyblock, &kdc_keyblock, &data); + &member_keyblock, &kdc_keyblock, 0, &data); if (ret) krb5_err(context, 1, ret, "_krb5_pac_sign"); @@ -244,7 +244,7 @@ main(int argc, char **argv) free(list); ret = _krb5_pac_sign(context, pac2, authtime, p, - &member_keyblock, &kdc_keyblock, &data); + &member_keyblock, &kdc_keyblock, 0, &data); if (ret) krb5_err(context, 1, ret, "_krb5_pac_sign 4"); @@ -343,7 +343,7 @@ main(int argc, char **argv) } ret = _krb5_pac_sign(context, pac, authtime, p, - &member_keyblock, &kdc_keyblock, &data); + &member_keyblock, &kdc_keyblock, 0, &data); if (ret) krb5_err(context, 1, ret, "_krb5_pac_sign"); diff --git a/lib/krb5/version-script.map b/lib/krb5/version-script.map index 980f7e486..3893e175d 100644 --- a/lib/krb5/version-script.map +++ b/lib/krb5/version-script.map @@ -807,6 +807,11 @@ HEIMDAL_KRB5_2.0 { _krb5_get_int; _krb5_get_int64; _krb5_pac_sign; + _krb5_kdc_pac_sign_ticket; + _krb5_kdc_pac_ticket_parse; + _krb5_pac_get_kdc_checksum_info; + _kdc_tkt_insert_pac; + _kdc_tkt_add_if_relevant_ad; _krb5_parse_moduli; _krb5_pk_kdf; _krb5_pk_load_id; diff --git a/tests/plugin/Makefile.am b/tests/plugin/Makefile.am index af1d07a8f..3fb1a2324 100644 --- a/tests/plugin/Makefile.am +++ b/tests/plugin/Makefile.am @@ -2,6 +2,9 @@ include $(top_srcdir)/Makefile.am.common +# for krb5_locl.h +AM_CPPFLAGS += -I$(srcdir)/../../lib/krb5 + noinst_DATA = krb5.conf SCRIPT_TESTS = check-pac diff --git a/tests/plugin/check-pac.in b/tests/plugin/check-pac.in index 5e43b2593..60ec21a31 100644 --- a/tests/plugin/check-pac.in +++ b/tests/plugin/check-pac.in @@ -47,6 +47,7 @@ testfailed="echo test failed; cat messages.log; exit 1" ../db/have-db || exit 77 R=TEST.H5L.SE +R2=TEST2.H5L.SE port=@port@ @@ -57,6 +58,7 @@ server=host/datan.test.h5l.se cache="FILE:${objdir}/cache.krb5" keytabfile=${objdir}/server.keytab keytab="FILE:${keytabfile}" +rodc_kvno="3058761729" kinit="${TESTS_ENVIRONMENT} ../../kuser/kinit -c $cache ${afs_no_afslog}" klist="${TESTS_ENVIRONMENT} ../../kuser/klist -c $cache" @@ -83,8 +85,19 @@ ${kadmin} \ ${kadmin} add -p foo --use-defaults foo@${R} || exit 1 ${kadmin} add -p bar --use-defaults ${server}@${R} || exit 1 +${kadmin} modify --kvno=$rodc_kvno "krbtgt/${R}@${R}" || exit 1 ${kadmin} ext -k ${keytab} ${server}@${R} || exit 1 +${kadmin} \ + init \ + --realm-max-ticket-life=1day \ + --realm-max-renewable-life=1month \ + ${R2} || exit 1 + +${kadmin} add -p foo --use-defaults foo@${R2} || exit 1 +${kadmin} add -p bar --use-defaults bar@${R2} || exit 1 +${kadmin} ext -k ${keytab} bar@${R2} || exit 1 + echo "Doing database check" ${kadmin} check ${R} || exit 1 ${kadmin} check ${R2} || exit 1 @@ -134,14 +147,24 @@ ${kgetcred} ${server}@${R} || { ec=1 ; eval "${testfailed}"; } echo "Verify PAC on server (no pac)"; > messages.log ${test_apreq} --verify-pac ${server}@${R} ${keytab} ${cache} 2> /dev/null && \ { ec=1 ; eval "${testfailed}"; } - -# now that verify-pac is the default, also verify that --no-verify-pac works ${test_apreq} ${server}@${R} ${keytab} ${cache} 2> /dev/null && \ { ec=1 ; eval "${testfailed}"; } +echo "Check the --no-verify-pac option"; > messages.log ${test_apreq} --no-verify-pac ${server}@${R} ${keytab} ${cache} 2> /dev/null || \ { ec=1 ; eval "${testfailed}"; } ${kdestroy} +echo "Getting client initial tickets (no pac - realm config)"; > messages.log +${kinit} --no-request-pac --password-file=${objdir}/foopassword foo@${R2} || \ + { ec=1 ; eval "${testfailed}"; } +echo "Getting tickets" ; > messages.log +${kgetcred} bar@${R2} || { ec=1 ; eval "${testfailed}"; } +echo "Verify PAC on server (no pac - realm config)"; > messages.log +${test_apreq} --verify-pac bar@${R2} ${keytab} ${cache} 2> /dev/null && \ + { ec=1 ; eval "${testfailed}"; } +${test_apreq} bar@${R2} ${keytab} ${cache} 2> /dev/null && \ + { ec=1 ; eval "${testfailed}"; } + echo "killing kdc (${kdcpid})" kill $kdcpid || exit 1 diff --git a/tests/plugin/krb5.conf.in b/tests/plugin/krb5.conf.in index 70939fd03..8ab2f1717 100644 --- a/tests/plugin/krb5.conf.in +++ b/tests/plugin/krb5.conf.in @@ -13,6 +13,10 @@ TEST.H5L.SE = { kdc = localhost:@port@ } + TEST2.H5L.SE = { + kdc = localhost:@port@ + disable_pac = true + } [kdc] database = { diff --git a/tests/plugin/windc.c b/tests/plugin/windc.c index 65d0ddf86..3828f1336 100644 --- a/tests/plugin/windc.c +++ b/tests/plugin/windc.c @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include @@ -52,16 +52,36 @@ pac_verify(void *ctx, krb5_context context, { krb5_error_code ret; krb5_data data; + krb5_cksumtype cstype; + uint16_t rodc_id; + krb5_enctype etype; + Key *key; krb5_warnx(context, "pac_verify"); ret = krb5_pac_get_buffer(context, *pac, 1, &data); if (ret) return ret; - krb5_data_free(&data); - return 0; + ret = _krb5_pac_get_kdc_checksum_info(context, *pac, &cstype, &rodc_id); + if (ret) + return ret; + + if (rodc_id == 0 || rodc_id != krbtgt->entry.kvno >> 16) { + krb5_warnx(context, "Wrong RODCIdentifier"); + return EINVAL; + } + + ret = krb5_cksumtype_to_enctype(context, cstype, &etype); + if (ret) + return ret; + + ret = hdb_enctype2key(context, &krbtgt->entry, NULL, etype, &key); + if (ret) + return ret; + + return krb5_pac_verify(context, *pac, 0, NULL, NULL, &key->key); } static krb5_error_code