1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-23 17:34:34 +03:00

kdc: sign ticket using Windows PAC

Split Windows PAC signing and verification logic, as the signing has to be when
the ticket is ready.

Create sign and verify the PAC KDC signature if the plugin did not, allowing
for S4U2Proxy to work, instead of KRB5SignedPath.

Use the header key to verify PAC server signature, as the same key used to
encrypt/decrypt the ticket should be used for PAC server signature, like U2U
tickets are signed witht the tgt session-key and not with the longterm key,
and so krbtgt should be no different and the header key should be used.

Lookup the delegated client in DB instead of passing the delegator DB entry.

Add PAC ticket-signatures and related functions.

Note: due to the change from KRB5SignedPath to PAC, S4U2Proxy requests
against new KDC will not work if the evidence ticket was acquired from
an old KDC, and vide versa.

Closes: #767

BUG: https://bugzilla.samba.org/show_bug.cgi?id=14642

[jsutton@samba.org Backported from Heimdal commit
 2ffaba9401d19c718764d4bd24180960290238e9
 - Removed tests
 - Adapted to Samba's version of Heimdal
 - Addressed build failures with -O3
 - Added knownfails
]

Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
This commit is contained in:
Isaac Boukris 2021-08-13 12:44:37 +03:00 committed by Andrew Bartlett
parent ccabc7f16c
commit d7b03394a9
10 changed files with 687 additions and 319 deletions

View File

@ -1,7 +1,7 @@
# #
# We expect all the MIT specific compatability tests to fail on heimdal # We expect all the MIT specific compatability tests to fail on heimdal
# kerberos # kerberos
^samba.tests.krb5.compatability_tests.samba.tests.krb5.compatability_tests.SimpleKerberosTests.test_mit_(?!ticket_signature) ^samba.tests.krb5.compatability_tests.samba.tests.krb5.compatability_tests.SimpleKerberosTests.test_mit_
# #
# Heimdal currently fails the following MS-KILE client principal lookup # Heimdal currently fails the following MS-KILE client principal lookup
# tests # tests
@ -68,14 +68,9 @@
^samba.tests.krb5.fast_tests.samba.tests.krb5.fast_tests.FAST_Tests.test_fast_inner_no_sname.ad_dc ^samba.tests.krb5.fast_tests.samba.tests.krb5.fast_tests.FAST_Tests.test_fast_inner_no_sname.ad_dc
^samba.tests.krb5.fast_tests.samba.tests.krb5.fast_tests.FAST_Tests.test_fast_tgs_inner_no_sname.ad_dc ^samba.tests.krb5.fast_tests.samba.tests.krb5.fast_tests.FAST_Tests.test_fast_tgs_inner_no_sname.ad_dc
# #
# Heimdal currently does not generate ticket signatures
#
^samba.tests.krb5.compatability_tests.samba.tests.krb5.compatability_tests.SimpleKerberosTests.test_heimdal_ticket_signature
#
# S4U tests # S4U tests
# #
^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_bronze_bit_rbcd_old_checksum ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_bronze_bit_rbcd_old_checksum
^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_constrained_delegation_missing_client_checksum
^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_constrained_delegation_no_service_pac ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_constrained_delegation_no_service_pac
^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_constrained_delegation_unkeyed_client_checksum ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_constrained_delegation_unkeyed_client_checksum
^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_constrained_delegation_unkeyed_service_checksum ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_constrained_delegation_unkeyed_service_checksum
@ -89,60 +84,13 @@
^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_unkeyed_service_checksum ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_unkeyed_service_checksum
^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_zeroed_client_checksum ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_zeroed_client_checksum
^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_zeroed_service_checksum ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_rbcd_zeroed_service_checksum
^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_client_not_delegated
^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_forwardable ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_forwardable
^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_not_forwardable
^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_not_trusted_empty_allowed ^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_not_trusted_empty_allowed
^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_not_trusted_nonempty_allowed
^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_trusted_empty_allowed
^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_trusted_nonempty_allowed
^samba.tests.krb5.s4u_tests.samba.tests.krb5.s4u_tests.S4UKerberosTests.test_s4u2self_without_forwardable
# #
# RODC tests # RODC tests
# #
^samba.tests.krb5.rodc_tests.samba.tests.krb5.rodc_tests.RodcKerberosTests.test_rodc_ticket_signature ^samba.tests.krb5.rodc_tests.samba.tests.krb5.rodc_tests.RodcKerberosTests.test_rodc_ticket_signature
# #
# PAC tests
#
^netr-bdc-arcfour.verify-sig-arcfour
^netr-bdc-arcfour.verify-sig-arcfour
^samba4.blackbox.pkinit_pac.STEP1 remote.pac verification.ad_dc:local
^samba4.blackbox.pkinit_pac.STEP1 remote.pac verification.ad_dc_ntvfs:local
^samba4.blackbox.pkinit_pac.netr-bdc-aes.verify-sig-aes.ad_dc:local
^samba4.blackbox.pkinit_pac.netr-bdc-aes.verify-sig-aes.ad_dc_ntvfs:local
^samba4.blackbox.pkinit_pac.netr-mem-aes.s4u2proxy-aes.ad_dc:local
^samba4.blackbox.pkinit_pac.netr-mem-aes.s4u2proxy-aes.ad_dc_ntvfs:local
^samba4.blackbox.pkinit_pac.netr-mem-aes.verify-sig-aes.ad_dc:local
^samba4.blackbox.pkinit_pac.netr-mem-aes.verify-sig-aes.ad_dc_ntvfs:local
^samba4.blackbox.pkinit_pac.netr-mem-arcfour.s4u2proxy-arcfour.ad_dc:local
^samba4.blackbox.pkinit_pac.netr-mem-arcfour.s4u2proxy-arcfour.ad_dc_ntvfs:local
^samba4.blackbox.pkinit_pac.netr-mem-arcfour.verify-sig-arcfour.ad_dc:local
^samba4.blackbox.pkinit_pac.netr-mem-arcfour.verify-sig-arcfour.ad_dc_ntvfs:local
^samba4.rpc.pac on ncacn_np.netr-bdc-aes.verify-sig-aes.fl2000dc
^samba4.rpc.pac on ncacn_np.netr-bdc-aes.verify-sig-aes.fl2003dc
^samba4.rpc.pac on ncacn_np.netr-bdc-aes.verify-sig-aes.fl2008dc
^samba4.rpc.pac on ncacn_np.netr-bdc-aes.verify-sig-aes.fl2008r2dc
^samba4.rpc.pac on ncacn_np.netr-bdc-arcfour.verify-sig-arcfour.fl2000dc
^samba4.rpc.pac on ncacn_np.netr-bdc-arcfour.verify-sig-arcfour.fl2003dc
^samba4.rpc.pac on ncacn_np.netr-bdc-arcfour.verify-sig-arcfour.fl2008dc
^samba4.rpc.pac on ncacn_np.netr-bdc-arcfour.verify-sig-arcfour.fl2008r2dc
^samba4.rpc.pac on ncacn_np.netr-mem-aes.s4u2proxy-aes.fl2000dc
^samba4.rpc.pac on ncacn_np.netr-mem-aes.s4u2proxy-aes.fl2003dc
^samba4.rpc.pac on ncacn_np.netr-mem-aes.s4u2proxy-aes.fl2008dc
^samba4.rpc.pac on ncacn_np.netr-mem-aes.s4u2proxy-aes.fl2008r2dc
^samba4.rpc.pac on ncacn_np.netr-mem-aes.verify-sig-aes.fl2000dc
^samba4.rpc.pac on ncacn_np.netr-mem-aes.verify-sig-aes.fl2003dc
^samba4.rpc.pac on ncacn_np.netr-mem-aes.verify-sig-aes.fl2008dc
^samba4.rpc.pac on ncacn_np.netr-mem-aes.verify-sig-aes.fl2008r2dc
^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.s4u2proxy-arcfour.fl2000dc
^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.s4u2proxy-arcfour.fl2003dc
^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.s4u2proxy-arcfour.fl2008dc
^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.s4u2proxy-arcfour.fl2008r2dc
^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.verify-sig-arcfour.fl2000dc
^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.verify-sig-arcfour.fl2003dc
^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.verify-sig-arcfour.fl2008dc
^samba4.rpc.pac on ncacn_np.netr-mem-arcfour.verify-sig-arcfour.fl2008r2dc
#
# The lack of KRB5SignedPath means we no longer return # The lack of KRB5SignedPath means we no longer return
# KRB5KRB_ERR_RESPONSE_TOO_BIG in this specific case # KRB5KRB_ERR_RESPONSE_TOO_BIG in this specific case
# #

View File

@ -1713,6 +1713,7 @@ _kdc_as_rep(krb5_context context,
if (send_pac_p(context, req)) { if (send_pac_p(context, req)) {
krb5_pac p = NULL; krb5_pac p = NULL;
krb5_data data; krb5_data data;
uint16_t rodc_id;
ret = _kdc_pac_generate(context, client, pk_reply_key, &p); ret = _kdc_pac_generate(context, client, pk_reply_key, &p);
if (ret) { if (ret) {
@ -1721,10 +1722,13 @@ _kdc_as_rep(krb5_context context,
goto out; goto out;
} }
if (p != NULL) { if (p != NULL) {
rodc_id = server->entry.kvno >> 16;
ret = _krb5_pac_sign(context, p, et.authtime, ret = _krb5_pac_sign(context, p, et.authtime,
client->entry.principal, client->entry.principal,
&skey->key, /* Server key */ &skey->key, /* Server key */
&skey->key, /* FIXME: should be krbtgt key */ &skey->key, /* FIXME: should be krbtgt key */
rodc_id,
&data); &data);
krb5_pac_free(context, p); krb5_pac_free(context, p);
if (ret) { if (ret) {
@ -1733,9 +1737,7 @@ _kdc_as_rep(krb5_context context,
goto out; goto out;
} }
ret = _kdc_tkt_add_if_relevant_ad(context, &et, ret = _kdc_tkt_insert_pac(context, &et, &data);
KRB5_AUTHDATA_WIN2K_PAC,
&data);
krb5_data_free(&data); krb5_data_free(&data);
if (ret) if (ret)
goto out; goto out;
@ -1889,64 +1891,3 @@ prepare_enc_data(krb5_context context,
return TRUE; return TRUE;
} }
/*
* 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;
}

View File

@ -59,85 +59,64 @@ check_PAC(krb5_context context,
hdb_entry_ex *client, hdb_entry_ex *client,
hdb_entry_ex *server, hdb_entry_ex *server,
hdb_entry_ex *krbtgt, hdb_entry_ex *krbtgt,
hdb_entry_ex *ticket_server,
const EncryptionKey *server_check_key, const EncryptionKey *server_check_key,
const EncryptionKey *server_sign_key, const EncryptionKey *krbtgt_check_key,
const EncryptionKey *krbtgt_sign_key,
EncTicketPart *tkt, EncTicketPart *tkt,
krb5_data *rspac, krb5_boolean *kdc_issued,
int *signedpath) krb5_pac *ppac)
{ {
AuthorizationData *ad = tkt->authorization_data; krb5_pac pac = NULL;
unsigned i, j;
krb5_error_code ret; krb5_error_code ret;
krb5_boolean signedticket;
if (ad == NULL || ad->len == 0) *kdc_issued = FALSE;
return 0; *ppac = NULL;
for (i = 0; i < ad->len; i++) { ret = _krb5_kdc_pac_ticket_parse(context, tkt, &signedticket, &pac);
AuthorizationData child; if (ret || pac == NULL)
return ret;
if (ad->val[i].ad_type != KRB5_AUTHDATA_IF_RELEVANT) /* Verify the server signature. */
continue; 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, /* Verify the KDC signatures. */
ad->val[i].ad_data.length, ret = _kdc_pac_verify(context, client_principal, delegated_proxy_principal,
&child, client, server, krbtgt, &pac);
NULL); if (ret == KRB5_PLUGIN_NO_HANDLE) {
if (ret) { /*
krb5_set_error_message(context, ret, "Failed to decode " * We can't verify the KDC signatures if the ticket was issued by
"IF_RELEVANT with %d", ret); * another realm's KDC.
return ret; */
} if (krb5_realm_compare(context, server->entry.principal,
for (j = 0; j < child.len; j++) { ticket_server->entry.principal)) {
ret = krb5_pac_verify(context, pac, 0, NULL, NULL,
if (child.val[j].ad_type == KRB5_AUTHDATA_WIN2K_PAC) { krbtgt_check_key);
int signed_pac = 0; if (ret) {
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);
}
krb5_pac_free(context, pac); krb5_pac_free(context, pac);
return ret; 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; return 0;
} }
@ -499,11 +478,12 @@ static krb5_error_code
tgs_make_reply(krb5_context context, tgs_make_reply(krb5_context context,
krb5_kdc_configuration *config, krb5_kdc_configuration *config,
KDC_REQ_BODY *b, KDC_REQ_BODY *b,
krb5_const_principal tgt_name, krb5_principal tgt_name,
const EncTicketPart *tgt, const EncTicketPart *tgt,
const krb5_keyblock *replykey, const krb5_keyblock *replykey,
int rk_is_subkey, int rk_is_subkey,
const EncryptionKey *serverkey, const EncryptionKey *serverkey,
const EncryptionKey *krbtgtkey,
const krb5_keyblock *sessionkey, const krb5_keyblock *sessionkey,
krb5_kvno kvno, krb5_kvno kvno,
AuthorizationData *auth_data, AuthorizationData *auth_data,
@ -513,8 +493,9 @@ tgs_make_reply(krb5_context context,
hdb_entry_ex *client, hdb_entry_ex *client,
krb5_principal client_principal, krb5_principal client_principal,
hdb_entry_ex *krbtgt, hdb_entry_ex *krbtgt,
krb5_enctype krbtgt_etype, krb5_pac mspac,
const krb5_data *rspac, uint16_t rodc_id,
krb5_boolean add_ticket_sig,
const METHOD_DATA *enc_pa_data, const METHOD_DATA *enc_pa_data,
const char **e_text, const char **e_text,
krb5_data *reply) krb5_data *reply)
@ -647,17 +628,6 @@ tgs_make_reply(krb5_context context,
if (!server->entry.flags.proxiable) if (!server->entry.flags.proxiable)
et.flags.proxiable = 0; et.flags.proxiable = 0;
if(rspac->length) {
/*
* 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) { if (auth_data) {
unsigned int i = 0; unsigned int i = 0;
@ -724,6 +694,11 @@ tgs_make_reply(krb5_context context,
is_weak = 1; is_weak = 1;
} }
/* 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 /* It is somewhat unclear where the etype in the following
encryption should come from. What we have is a session encryption should come from. What we have is a session
@ -910,6 +885,7 @@ tgs_parse_request(krb5_context context,
int **cusec, int **cusec,
AuthorizationData **auth_data, AuthorizationData **auth_data,
krb5_keyblock **replykey, krb5_keyblock **replykey,
Key **header_key,
int *rk_is_subkey) int *rk_is_subkey)
{ {
static char failed[] = "<unparse_name failed>"; static char failed[] = "<unparse_name failed>";
@ -1047,6 +1023,8 @@ tgs_parse_request(krb5_context context,
goto out; goto out;
} }
*header_key = tkey;
{ {
krb5_authenticator auth; krb5_authenticator auth;
@ -1236,6 +1214,57 @@ eout:
return ENOMEM; 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 static krb5_error_code
tgs_build_reply(krb5_context context, tgs_build_reply(krb5_context context,
krb5_kdc_configuration *config, krb5_kdc_configuration *config,
@ -1243,6 +1272,7 @@ tgs_build_reply(krb5_context context,
KDC_REQ_BODY *b, KDC_REQ_BODY *b,
hdb_entry_ex *krbtgt, hdb_entry_ex *krbtgt,
krb5_enctype krbtgt_etype, krb5_enctype krbtgt_etype,
Key *tkey_check,
const krb5_keyblock *replykey, const krb5_keyblock *replykey,
int rk_is_subkey, int rk_is_subkey,
krb5_ticket *ticket, krb5_ticket *ticket,
@ -1263,7 +1293,9 @@ tgs_build_reply(krb5_context context,
const EncryptionKey *ekey; const EncryptionKey *ekey;
krb5_keyblock sessionkey; krb5_keyblock sessionkey;
krb5_kvno kvno; krb5_kvno kvno;
krb5_data rspac; krb5_pac mspac = NULL;
uint16_t rodc_id;
krb5_boolean add_ticket_sig = FALSE;
hdb_entry_ex *krbtgt_out = NULL; hdb_entry_ex *krbtgt_out = NULL;
@ -1274,15 +1306,13 @@ tgs_build_reply(krb5_context context,
int nloop = 0; int nloop = 0;
EncTicketPart adtkt; EncTicketPart adtkt;
char opt_str[128]; char opt_str[128];
int signedpath = 0; krb5_boolean kdc_issued = FALSE;
Key *tkey_check;
Key *tkey_sign; Key *tkey_sign;
int flags = HDB_F_FOR_TGS_REQ; int flags = HDB_F_FOR_TGS_REQ;
memset(&sessionkey, 0, sizeof(sessionkey)); memset(&sessionkey, 0, sizeof(sessionkey));
memset(&adtkt, 0, sizeof(adtkt)); memset(&adtkt, 0, sizeof(adtkt));
krb5_data_zero(&rspac);
memset(&enc_pa_data, 0, sizeof(enc_pa_data)); memset(&enc_pa_data, 0, sizeof(enc_pa_data));
s = b->sname; s = b->sname;
@ -1517,18 +1547,6 @@ server_lookup:
* backward. * backward.
*/ */
/*
* Validate authoriation data
*/
ret = hdb_enctype2key(context, &krbtgt->entry,
krbtgt_etype, &tkey_check);
if(ret) {
kdc_log(context, config, 0,
"Failed to find key for krbtgt PAC check");
goto out;
}
/* Now refetch the primary krbtgt, and get the current kvno (the /* Now refetch the primary krbtgt, and get the current kvno (the
* sign check may have been on an old kvno, and the server may * sign check may have been on an old kvno, and the server may
* have been an incoming trust) */ * have been an incoming trust) */
@ -1589,41 +1607,14 @@ server_lookup:
goto out; goto out;
} }
ret = _kdc_db_fetch(context, config, cp, HDB_F_GET_CLIENT | flags, ret = db_fetch_client(context, config, flags, cp, cpn,
NULL, &clientdb, &client); krb5_principal_get_realm(context, krbtgt_out->entry.principal),
if(ret == HDB_ERR_NOT_FOUND_HERE) { &clientdb, &client);
/* This is OK, we are just trying to find out if they have if (ret)
* been disabled or deleted in the meantime, missing secrets goto out;
* is OK */
} else if(ret){
const char *krbtgt_realm, *msg;
/* ret = check_PAC(context, config, cp, NULL, client, server, krbtgt, krbtgt,
* If the client belongs to the same realm as our krbtgt, it &tkey_check->key, &tkey_check->key, tgt, &kdc_issued, &mspac);
* 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, 1, "Client no longer in database: %s",
cpn);
goto out;
}
msg = krb5_get_error_message(context, ret);
kdc_log(context, config, 1, "Client not found in database: %s", msg);
krb5_free_error_message(context, msg);
}
ret = check_PAC(context, config, cp, NULL,
client, server, krbtgt,
&tkey_check->key,
ekey, &tkey_sign->key,
tgt, &rspac, &signedpath);
if (ret) { if (ret) {
const char *msg = krb5_get_error_message(context, ret); const char *msg = krb5_get_error_message(context, ret);
kdc_log(context, config, 0, kdc_log(context, config, 0,
@ -1760,27 +1751,15 @@ server_lookup:
goto out; goto out;
/* If we were about to put a PAC into the ticket, we better fix it to be the right PAC */ /* If we were about to put a PAC into the ticket, we better fix it to be the right PAC */
if(rspac.data) { if (mspac) {
krb5_pac p = NULL; krb5_pac_free(context, mspac);
krb5_data_free(&rspac); mspac = NULL;
ret = _kdc_pac_generate(context, s4u2self_impersonated_client, NULL, &p); ret = _kdc_pac_generate(context, s4u2self_impersonated_client, NULL, &mspac);
if (ret) { if (ret) {
kdc_log(context, config, 0, "PAC generation failed for -- %s", kdc_log(context, config, 0, "PAC generation failed for -- %s",
tpn); tpn);
goto out; 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, 0, "PAC signing failed for -- %s",
tpn);
goto out;
}
}
} }
/* /*
@ -1823,22 +1802,25 @@ server_lookup:
&& b->additional_tickets->len != 0 && b->additional_tickets->len != 0
&& b->kdc_options.enc_tkt_in_skey == 0) && 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; Key *clientkey;
Ticket *t; Ticket *t;
/* /*
* Require that the KDC have issued the service's krbtgt (not * We require that the service's krbtgt has a PAC.
* self-issued ticket with kimpersonate(1).
*/ */
if (!signedpath) { if (mspac == NULL) {
ret = KRB5KDC_ERR_BADOPTION; ret = KRB5KDC_ERR_BADOPTION;
kdc_log(context, config, 0, kdc_log(context, config, 0,
"Constrained delegation done on service ticket %s/%s", "Constrained delegation without PAC %s/%s",
cpn, spn); cpn, spn);
goto out; goto out;
} }
krb5_pac_free(context, mspac);
mspac = NULL;
t = &b->additional_tickets->val[0]; t = &b->additional_tickets->val[0];
ret = hdb_enctype2key(context, &client->entry, ret = hdb_enctype2key(context, &client->entry,
@ -1902,19 +1884,32 @@ server_lookup:
goto out; goto out;
} }
krb5_data_free(&rspac); /* Try lookup the delegated client in DB */
ret = db_fetch_client(context, config, flags, tp, tpn,
krb5_principal_get_realm(context, krbtgt_out->entry.principal),
NULL, &adclient);
if (ret)
goto out;
if (adclient != NULL) {
ret = kdc_check_flags(context, config,
adclient, tpn,
server, spn,
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 * TODO: pass in t->sname and t->realm and build
* a S4U_DELEGATION_INFO blob to the PAC. * a S4U_DELEGATION_INFO blob to the PAC.
*/ */
ret = check_PAC(context, config, tp, dp, ret = check_PAC(context, config, tp, dp, adclient, server, krbtgt, client,
client, server, krbtgt, &clientkey->key, &tkey_check->key, &adtkt, &ad_kdc_issued, &mspac);
&clientkey->key, if (adclient)
ekey, &tkey_sign->key, _kdc_free_ent(context, adclient);
&adtkt, &rspac, &ad_signedpath);
if (ret) { if (ret) {
const char *msg = krb5_get_error_message(context, ret); const char *msg = krb5_get_error_message(context, ret);
kdc_log(context, config, 0, kdc_log(context, config, 0,
@ -1925,13 +1920,12 @@ server_lookup:
goto out; goto out;
} }
if (!ad_signedpath) { if (mspac == NULL || !ad_kdc_issued) {
ret = KRB5KDC_ERR_BADOPTION; ret = KRB5KDC_ERR_BADOPTION;
kdc_log(context, config, 0, kdc_log(context, config, 0,
"Ticket not signed with PAC nor SignedPath service %s failed " "Ticket not signed with PAC; service %s failed for "
"for delegation to %s for client %s (%s)" "for delegation to %s for client %s (%s) from %s; (%s).",
"from %s", spn, tpn, dpn, cpn, from, mspac ? "Ticket unsigned" : "No PAC");
spn, tpn, dpn, cpn, from);
goto out; goto out;
} }
@ -2000,6 +1994,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;
/* /*
* *
*/ */
@ -2012,6 +2025,7 @@ server_lookup:
replykey, replykey,
rk_is_subkey, rk_is_subkey,
ekey, ekey,
&tkey_sign->key,
&sessionkey, &sessionkey,
kvno, kvno,
*auth_data, *auth_data,
@ -2021,8 +2035,9 @@ server_lookup:
client, client,
cp, cp,
krbtgt_out, krbtgt_out,
krbtgt_etype, mspac,
&rspac, rodc_id,
add_ticket_sig,
&enc_pa_data, &enc_pa_data,
e_text, e_text,
reply); reply);
@ -2035,7 +2050,6 @@ out:
if (dpn) if (dpn)
free(dpn); free(dpn);
krb5_data_free(&rspac);
krb5_free_keyblock_contents(context, &sessionkey); krb5_free_keyblock_contents(context, &sessionkey);
if(krbtgt_out) if(krbtgt_out)
_kdc_free_ent(context, krbtgt_out); _kdc_free_ent(context, krbtgt_out);
@ -2060,6 +2074,9 @@ out:
free_EncTicketPart(&adtkt); free_EncTicketPart(&adtkt);
if (mspac)
krb5_pac_free(context, mspac);
return ret; return ret;
} }
@ -2080,6 +2097,7 @@ _kdc_tgs_rep(krb5_context context,
krb5_error_code ret; krb5_error_code ret;
int i = 0; int i = 0;
const PA_DATA *tgs_req; const PA_DATA *tgs_req;
Key *header_key = NULL;
hdb_entry_ex *krbtgt = NULL; hdb_entry_ex *krbtgt = NULL;
krb5_ticket *ticket = NULL; krb5_ticket *ticket = NULL;
@ -2117,6 +2135,7 @@ _kdc_tgs_rep(krb5_context context,
&csec, &cusec, &csec, &cusec,
&auth_data, &auth_data,
&replykey, &replykey,
&header_key,
&rk_is_subkey); &rk_is_subkey);
if (ret == HDB_ERR_NOT_FOUND_HERE) { if (ret == HDB_ERR_NOT_FOUND_HERE) {
/* kdc_log() is called in tgs_parse_request() */ /* kdc_log() is called in tgs_parse_request() */
@ -2134,6 +2153,7 @@ _kdc_tgs_rep(krb5_context context,
&req->req_body, &req->req_body,
krbtgt, krbtgt,
krbtgt_etype, krbtgt_etype,
header_key,
replykey, replykey,
rk_is_subkey, rk_is_subkey,
ticket, ticket,

View File

@ -77,8 +77,14 @@ _kdc_pac_generate(krb5_context context,
krb5_pac *pac) krb5_pac *pac)
{ {
*pac = NULL; *pac = NULL;
if (windcft == NULL) if (krb5_config_get_bool_default(context, NULL, FALSE, "realms",
client->entry.principal->realm,
"disable_pac", NULL))
return 0; return 0;
if (windcft == NULL) {
return krb5_pac_init(context, pac);
}
if (windcft->pac_pk_generate != NULL && pk_reply_key != NULL) if (windcft->pac_pk_generate != NULL && pk_reply_key != NULL)
return (windcft->pac_pk_generate)(windcctx, context, return (windcft->pac_pk_generate)(windcctx, context,
client, pk_reply_key, pac); client, pk_reply_key, pac);
@ -92,20 +98,17 @@ _kdc_pac_verify(krb5_context context,
hdb_entry_ex *client, hdb_entry_ex *client,
hdb_entry_ex *server, hdb_entry_ex *server,
hdb_entry_ex *krbtgt, hdb_entry_ex *krbtgt,
krb5_pac *pac, krb5_pac *pac)
int *verified)
{ {
krb5_error_code ret; krb5_error_code ret;
if (windcft == NULL) if (windcft == NULL)
return 0; return KRB5_PLUGIN_NO_HANDLE;
ret = windcft->pac_verify(windcctx, context, ret = windcft->pac_verify(windcctx, context,
client_principal, client_principal,
delegated_proxy_principal, delegated_proxy_principal,
client, server, krbtgt, pac); client, server, krbtgt, pac);
if (ret == 0)
*verified = 1;
return ret; return ret;
} }

View File

@ -43,8 +43,9 @@
* krb5_pac_init and fill in the PAC structure for the principal using * krb5_pac_init and fill in the PAC structure for the principal using
* krb5_pac_add_buffer. * krb5_pac_add_buffer.
* *
* The PAC verify function should verify all components in the PAC * The PAC verify function should verify the PAC KDC signatures by fetching
* using krb5_pac_get_types and krb5_pac_get_buffer for all types. * 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. * Check client access function check if the client is authorized.
*/ */

View File

@ -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;
}

View File

@ -53,6 +53,8 @@ struct krb5_pac_data {
struct PAC_INFO_BUFFER *server_checksum; struct PAC_INFO_BUFFER *server_checksum;
struct PAC_INFO_BUFFER *privsvr_checksum; struct PAC_INFO_BUFFER *privsvr_checksum;
struct PAC_INFO_BUFFER *logon_name; struct PAC_INFO_BUFFER *logon_name;
struct PAC_INFO_BUFFER *ticket_checksum;
krb5_data ticket_sign_data;
}; };
#define PAC_ALIGNMENT 8 #define PAC_ALIGNMENT 8
@ -64,6 +66,7 @@ struct krb5_pac_data {
#define PAC_PRIVSVR_CHECKSUM 7 #define PAC_PRIVSVR_CHECKSUM 7
#define PAC_LOGON_NAME 10 #define PAC_LOGON_NAME 10
#define PAC_CONSTRAINED_DELEGATION 11 #define PAC_CONSTRAINED_DELEGATION 11
#define PAC_TICKET_CHECKSUM 16
#define CHECK(r,f,l) \ #define CHECK(r,f,l) \
do { \ do { \
@ -142,13 +145,13 @@ krb5_pac_parse(krb5_context context, const void *ptr, size_t len,
CHECK(ret, krb5_ret_uint32(sp, &tmp2), out); CHECK(ret, krb5_ret_uint32(sp, &tmp2), out);
if (tmp < 1) { if (tmp < 1) {
ret = EINVAL; /* Too few buffers */ 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; goto out;
} }
if (tmp2 != 0) { if (tmp2 != 0) {
ret = EINVAL; /* Wrong version */ ret = EINVAL; /* Wrong version */
krb5_set_error_message(context, ret, krb5_set_error_message(context, ret,
N_("PAC have wrong version %d", ""), N_("PAC has wrong version %d", ""),
(int)tmp2); (int)tmp2);
goto out; goto out;
} }
@ -191,7 +194,7 @@ krb5_pac_parse(krb5_context context, const void *ptr, size_t len,
if (p->pac->buffers[i].offset_lo > len) { if (p->pac->buffers[i].offset_lo > len) {
ret = EINVAL; ret = EINVAL;
krb5_set_error_message(context, ret, krb5_set_error_message(context, ret,
N_("PAC offset off end", "")); N_("PAC offset overflow", ""));
goto out; goto out;
} }
if (p->pac->buffers[i].offset_lo < header_end) { if (p->pac->buffers[i].offset_lo < header_end) {
@ -204,7 +207,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){ if (p->pac->buffers[i].buffersize > len - p->pac->buffers[i].offset_lo){
ret = EINVAL; 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; goto out;
} }
@ -213,7 +216,7 @@ krb5_pac_parse(krb5_context context, const void *ptr, size_t len,
if (p->server_checksum) { if (p->server_checksum) {
ret = EINVAL; ret = EINVAL;
krb5_set_error_message(context, ret, krb5_set_error_message(context, ret,
N_("PAC have two server checksums", "")); N_("PAC has multiple server checksums", ""));
goto out; goto out;
} }
p->server_checksum = &p->pac->buffers[i]; p->server_checksum = &p->pac->buffers[i];
@ -221,7 +224,7 @@ krb5_pac_parse(krb5_context context, const void *ptr, size_t len,
if (p->privsvr_checksum) { if (p->privsvr_checksum) {
ret = EINVAL; ret = EINVAL;
krb5_set_error_message(context, ret, krb5_set_error_message(context, ret,
N_("PAC have two KDC checksums", "")); N_("PAC has multiple KDC checksums", ""));
goto out; goto out;
} }
p->privsvr_checksum = &p->pac->buffers[i]; p->privsvr_checksum = &p->pac->buffers[i];
@ -229,10 +232,18 @@ krb5_pac_parse(krb5_context context, const void *ptr, size_t len,
if (p->logon_name) { if (p->logon_name) {
ret = EINVAL; ret = EINVAL;
krb5_set_error_message(context, ret, krb5_set_error_message(context, ret,
N_("PAC have two logon names", "")); N_("PAC has multiple logon names", ""));
goto out; goto out;
} }
p->logon_name = &p->pac->buffers[i]; 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];
} }
} }
@ -425,6 +436,7 @@ KRB5_LIB_FUNCTION void KRB5_LIB_CALL
krb5_pac_free(krb5_context context, krb5_pac pac) krb5_pac_free(krb5_context context, krb5_pac pac)
{ {
krb5_data_free(&pac->data); krb5_data_free(&pac->data);
krb5_data_free(&pac->ticket_sign_data);
free(pac->pac); free(pac->pac);
free(pac); free(pac);
} }
@ -444,6 +456,7 @@ verify_checksum(krb5_context context,
uint32_t type; uint32_t type;
krb5_error_code ret; krb5_error_code ret;
Checksum cksum; Checksum cksum;
size_t cksumsize;
memset(&cksum, 0, sizeof(cksum)); memset(&cksum, 0, sizeof(cksum));
@ -456,8 +469,17 @@ verify_checksum(krb5_context context,
CHECK(ret, krb5_ret_uint32(sp, &type), out); CHECK(ret, krb5_ret_uint32(sp, &type), out);
cksum.cksumtype = type; 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); cksum.checksum.data = malloc(cksum.checksum.length);
if (cksum.checksum.data == NULL) { if (cksum.checksum.data == NULL) {
ret = krb5_enomem(context); ret = krb5_enomem(context);
@ -804,7 +826,6 @@ out:
return ret; return ret;
} }
/** /**
* Verify the PAC. * Verify the PAC.
* *
@ -844,18 +865,22 @@ krb5_pac_verify(krb5_context context,
return EINVAL; return EINVAL;
} }
ret = verify_logonname(context, if (principal != NULL) {
pac->logon_name, ret = verify_logonname(context, pac->logon_name, &pac->data, authtime,
&pac->data, principal);
authtime, if (ret)
principal); return ret;
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 * in the service case, clean out data option of the privsvr and
* server checksum before checking the checksum. * server checksum before checking the checksum.
*/ */
if (server != NULL)
{ {
krb5_data *copy; krb5_data *copy;
@ -897,6 +922,20 @@ krb5_pac_verify(krb5_context context,
privsvr); privsvr);
if (ret) if (ret)
return 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; return 0;
@ -965,13 +1004,14 @@ _krb5_pac_sign(krb5_context context,
krb5_principal principal, krb5_principal principal,
const krb5_keyblock *server_key, const krb5_keyblock *server_key,
const krb5_keyblock *priv_key, const krb5_keyblock *priv_key,
uint16_t rodc_id,
krb5_data *data) krb5_data *data)
{ {
krb5_error_code ret; krb5_error_code ret;
krb5_storage *sp = NULL, *spdata = NULL; krb5_storage *sp = NULL, *spdata = NULL;
uint32_t end; uint32_t end;
size_t server_size, priv_size; 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; uint32_t server_cksumtype = 0, priv_cksumtype = 0;
int num = 0; int num = 0;
size_t i; size_t i;
@ -985,9 +1025,9 @@ _krb5_pac_sign(krb5_context context,
p->server_checksum = &p->pac->buffers[i]; p->server_checksum = &p->pac->buffers[i];
} }
if (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, krb5_set_error_message(context, ret,
N_("PAC have two server checksums", "")); N_("PAC has multiple server checksums", ""));
goto out; goto out;
} }
} else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) { } else if (p->pac->buffers[i].type == PAC_PRIVSVR_CHECKSUM) {
@ -995,9 +1035,9 @@ _krb5_pac_sign(krb5_context context,
p->privsvr_checksum = &p->pac->buffers[i]; p->privsvr_checksum = &p->pac->buffers[i];
} }
if (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, krb5_set_error_message(context, ret,
N_("PAC have two KDC checksums", "")); N_("PAC has multiple KDC checksums", ""));
goto out; goto out;
} }
} else if (p->pac->buffers[i].type == PAC_LOGON_NAME) { } else if (p->pac->buffers[i].type == PAC_LOGON_NAME) {
@ -1005,9 +1045,19 @@ _krb5_pac_sign(krb5_context context,
p->logon_name = &p->pac->buffers[i]; p->logon_name = &p->pac->buffers[i];
} }
if (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, 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; goto out;
} }
} }
@ -1019,6 +1069,8 @@ _krb5_pac_sign(krb5_context context,
num++; num++;
if (p->privsvr_checksum == NULL) if (p->privsvr_checksum == NULL)
num++; num++;
if (p->ticket_sign_data.length != 0 && p->ticket_checksum == NULL)
num++;
if (num) { if (num) {
void *ptr; void *ptr;
@ -1044,6 +1096,11 @@ _krb5_pac_sign(krb5_context context,
memset(p->privsvr_checksum, 0, sizeof(*p->privsvr_checksum)); memset(p->privsvr_checksum, 0, sizeof(*p->privsvr_checksum));
p->privsvr_checksum->type = PAC_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 */ /* Calculate LOGON NAME */
@ -1055,6 +1112,7 @@ _krb5_pac_sign(krb5_context context,
ret = pac_checksum(context, server_key, &server_cksumtype, &server_size); ret = pac_checksum(context, server_key, &server_cksumtype, &server_size);
if (ret) if (ret)
goto out; goto out;
ret = pac_checksum(context, priv_key, &priv_cksumtype, &priv_size); ret = pac_checksum(context, priv_key, &priv_cksumtype, &priv_size);
if (ret) if (ret)
goto out; goto out;
@ -1095,10 +1153,24 @@ _krb5_pac_sign(krb5_context context,
priv_offset = end + 4; priv_offset = end + 4;
CHECK(ret, krb5_store_uint32(spdata, priv_cksumtype), out); CHECK(ret, krb5_store_uint32(spdata, priv_cksumtype), out);
CHECK(ret, fill_zeros(context, spdata, priv_size), 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) { } else if (p->pac->buffers[i].type == PAC_LOGON_NAME) {
len = krb5_storage_write(spdata, logon.data, logon.length); len = krb5_storage_write(spdata, logon.data, logon.length);
if (logon.length != len) { if (logon.length != len) {
ret = EINVAL; ret = KRB5KDC_ERR_BADOPTION;
goto out; goto out;
} }
} else { } else {
@ -1156,6 +1228,16 @@ _krb5_pac_sign(krb5_context context,
} }
/* sign */ /* 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, ret = create_checksum(context, server_key, server_cksumtype,
d.data, d.length, d.data, d.length,
(char *)d.data + server_offset, server_size); (char *)d.data + server_offset, server_size);
@ -1171,6 +1253,32 @@ _krb5_pac_sign(krb5_context context,
goto out; 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 */ /* done */
*data = d; *data = d;
@ -1187,3 +1295,221 @@ out:
krb5_storage_free(spdata); krb5_storage_free(spdata);
return ret; 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);
}

View File

@ -751,6 +751,11 @@ HEIMDAL_KRB5_2.0 {
_krb5_get_host_realm_int; _krb5_get_host_realm_int;
_krb5_get_int; _krb5_get_int;
_krb5_pac_sign; _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_parse_moduli;
_krb5_pk_kdf; _krb5_pk_kdf;
_krb5_pk_load_id; _krb5_pk_load_id;

View File

@ -621,7 +621,7 @@ if not bld.CONFIG_SET("USING_SYSTEM_KRB5"):
KRB5_SOURCE = [os.path.join('lib/krb5/', x) for x in TO_LIST( KRB5_SOURCE = [os.path.join('lib/krb5/', x) for x in TO_LIST(
'''acache.c add_et_list.c '''acache.c add_et_list.c
addr_families.c appdefault.c addr_families.c appdefault.c
asn1_glue.c auth_context.c asn1_glue.c auth_context.c authdata.c
build_ap_req.c build_auth.c cache.c build_ap_req.c build_auth.c cache.c
changepw.c codec.c config_file.c changepw.c codec.c config_file.c
constants.c convert_creds.c constants.c convert_creds.c

View File

@ -913,7 +913,7 @@ for env in ['fileserver_smb1', 'nt4_member', 'clusteredmember', 'ktest', 'nt4_dc
planoldpythontestsuite(env, "samba.tests.imports") planoldpythontestsuite(env, "samba.tests.imports")
have_fast_support = int('SAMBA_USES_MITKDC' in config_hash) have_fast_support = int('SAMBA_USES_MITKDC' in config_hash)
tkt_sig_support = 0 tkt_sig_support = int('SAMBA4_USES_HEIMDAL' in config_hash)
planoldpythontestsuite("none", "samba.tests.krb5.kcrypto") planoldpythontestsuite("none", "samba.tests.krb5.kcrypto")
planoldpythontestsuite("ad_dc_default", "samba.tests.krb5.simple_tests", planoldpythontestsuite("ad_dc_default", "samba.tests.krb5.simple_tests",
environ={'SERVICE_USERNAME':'$SERVER', environ={'SERVICE_USERNAME':'$SERVER',