kdc: sign ticket using Windows PAC
authorIsaac Boukris <iboukris@gmail.com>
Fri, 13 Aug 2021 09:44:37 +0000 (12:44 +0300)
committerStefan Metzmacher <metze@samba.org>
Tue, 26 Oct 2021 12:00:27 +0000 (12:00 +0000)
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
BUG: https://bugzilla.samba.org/show_bug.cgi?id=14881

[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>
[abartlet@samba.org backported from commit d7b03394a9012960d71489e775d40d10fd6f5232
 due to conflicts in knownfail due to missing tests that crash the
 MIT KDC]

selftest/knownfail_heimdal_kdc
source4/heimdal/kdc/kerberos5.c
source4/heimdal/kdc/krb5tgs.c
source4/heimdal/kdc/windc.c
source4/heimdal/kdc/windc_plugin.h
source4/heimdal/lib/krb5/authdata.c [new file with mode: 0644]
source4/heimdal/lib/krb5/pac.c
source4/heimdal/lib/krb5/version-script.map
source4/heimdal_build/wscript_build
source4/selftest/tests.py

index f0263133eee4e3c3cea7ee3a0a137c3e0001399c..4ec682c01d020352ec70ca5fe5474207c286c167 100644 (file)
@@ -1,7 +1,7 @@
 #
 # We expect all the MIT specific compatability tests to fail on heimdal
 # 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
 # tests
 ^samba.tests.krb5.fast_tests.samba.tests.krb5.fast_tests.FAST_Tests.test_fast_tgs_outer_no_sname.ad_dc
 ^samba.tests.krb5.fast_tests.samba.tests.krb5.fast_tests.FAST_Tests.test_fast_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
 #
 ^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_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_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_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_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_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
 #
 ^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
 # KRB5KRB_ERR_RESPONSE_TOO_BIG in this specific case
 #
index a400b21a652240d915670ad8ea4321ed038c6c1c..7b17d2539cef0ca8242c433b8bd3e2a81de62639 100644 (file)
@@ -1712,6 +1712,7 @@ _kdc_as_rep(krb5_context context,
     if (send_pac_p(context, req)) {
        krb5_pac p = NULL;
        krb5_data data;
+       uint16_t rodc_id;
 
        ret = _kdc_pac_generate(context, client, pk_reply_key, &p);
        if (ret) {
@@ -1720,10 +1721,13 @@ _kdc_as_rep(krb5_context context,
            goto out;
        }
        if (p != NULL) {
+           rodc_id = server->entry.kvno >> 16;
+
            ret = _krb5_pac_sign(context, p, et.authtime,
                                 client->entry.principal,
                                 &skey->key, /* Server key */
                                 &skey->key, /* FIXME: should be krbtgt key */
+                                rodc_id,
                                 &data);
            krb5_pac_free(context, p);
            if (ret) {
@@ -1732,9 +1736,7 @@ _kdc_as_rep(krb5_context context,
                goto out;
            }
 
-           ret = _kdc_tkt_add_if_relevant_ad(context, &et,
-                                             KRB5_AUTHDATA_WIN2K_PAC,
-                                             &data);
+           ret = _kdc_tkt_insert_pac(context, &et, &data);
            krb5_data_free(&data);
            if (ret)
                goto out;
@@ -1888,64 +1890,3 @@ prepare_enc_data(krb5_context context,
 
        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;
-}
index c6bab82f5173be3157441940fb121125bfd17ddb..3b35b82840223c9130c8b78bcc0504d109321aa1 100644 (file)
@@ -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;
-
-    for (i = 0; i < ad->len; i++) {
-       AuthorizationData child;
-
-       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 "
-                                  "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;
+    *kdc_issued = FALSE;
+    *ppac = NULL;
 
-               ret = krb5_pac_verify(context, pac, tkt->authtime,
-                                     client_principal,
-                                     server_check_key, NULL);
-               if (ret) {
-                   krb5_pac_free(context, pac);
-                   return ret;
-               }
+    ret = _krb5_kdc_pac_ticket_parse(context, tkt, &signedticket, &pac);
+    if (ret || pac == NULL)
+       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;
-               }
+    /* 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;
+    }
 
-               /*
-                * 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;
 }
 
@@ -499,11 +478,12 @@ static krb5_error_code
 tgs_make_reply(krb5_context context,
               krb5_kdc_configuration *config,
               KDC_REQ_BODY *b,
-              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,
@@ -513,8 +493,9 @@ tgs_make_reply(krb5_context context,
               hdb_entry_ex *client,
               krb5_principal client_principal,
               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,
               const char **e_text,
               krb5_data *reply)
@@ -647,17 +628,6 @@ tgs_make_reply(krb5_context context,
     if (!server->entry.flags.proxiable)
         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) {
        unsigned int i = 0;
 
@@ -724,6 +694,11 @@ tgs_make_reply(krb5_context context,
        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
        encryption should come from. What we have is a session
@@ -910,6 +885,7 @@ tgs_parse_request(krb5_context context,
                  int **cusec,
                  AuthorizationData **auth_data,
                  krb5_keyblock **replykey,
+                 Key **header_key,
                  int *rk_is_subkey)
 {
     static char failed[] = "<unparse_name failed>";
@@ -1047,6 +1023,8 @@ tgs_parse_request(krb5_context context,
        goto out;
     }
 
+    *header_key = tkey;
+
     {
        krb5_authenticator auth;
 
@@ -1236,6 +1214,57 @@ 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(krb5_context context,
                krb5_kdc_configuration *config,
@@ -1243,6 +1272,7 @@ tgs_build_reply(krb5_context context,
                KDC_REQ_BODY *b,
                hdb_entry_ex *krbtgt,
                krb5_enctype krbtgt_etype,
+               Key *tkey_check,
                const krb5_keyblock *replykey,
                int rk_is_subkey,
                krb5_ticket *ticket,
@@ -1263,7 +1293,9 @@ tgs_build_reply(krb5_context context,
     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;
 
     hdb_entry_ex *krbtgt_out = NULL;
 
@@ -1274,15 +1306,13 @@ tgs_build_reply(krb5_context context,
     int nloop = 0;
     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;
@@ -1517,18 +1547,6 @@ server_lookup:
      * 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
      * sign check may have been on an old kvno, and the server may
      * have been an incoming trust) */
@@ -1589,41 +1607,14 @@ server_lookup:
        goto out;
     }
 
-    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
-        * 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, 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 = db_fetch_client(context, config, flags, cp, cpn,
+                         krb5_principal_get_realm(context, krbtgt_out->entry.principal),
+                         &clientdb, &client);
+    if (ret)
+       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_log(context, config, 0,
@@ -1760,27 +1751,15 @@ server_lookup:
                goto out;
 
            /* 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, NULL, &p);
+           if (mspac) {
+               krb5_pac_free(context, mspac);
+               mspac = NULL;
+               ret = _kdc_pac_generate(context, s4u2self_impersonated_client, NULL, &mspac);
                if (ret) {
                    kdc_log(context, config, 0, "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, 0, "PAC signing failed for -- %s",
-                               tpn);
-                       goto out;
-                   }
-               }
            }
 
            /*
@@ -1823,22 +1802,25 @@ server_lookup:
        && b->additional_tickets->len != 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;
        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_log(context, config, 0,
-                   "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,
@@ -1902,19 +1884,32 @@ server_lookup:
            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
         * 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_log(context, config, 0,
@@ -1925,13 +1920,12 @@ server_lookup:
            goto out;
        }
 
-       if (!ad_signedpath) {
+       if (mspac == NULL || !ad_kdc_issued) {
            ret = KRB5KDC_ERR_BADOPTION;
            kdc_log(context, config, 0,
-                   "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");
            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,
                         rk_is_subkey,
                         ekey,
+                        &tkey_sign->key,
                         &sessionkey,
                         kvno,
                         *auth_data,
@@ -2021,8 +2035,9 @@ server_lookup:
                         client,
                         cp,
                         krbtgt_out,
-                        krbtgt_etype,
-                        &rspac,
+                        mspac,
+                        rodc_id,
+                        add_ticket_sig,
                         &enc_pa_data,
                         e_text,
                         reply);
@@ -2035,7 +2050,6 @@ out:
     if (dpn)
        free(dpn);
 
-    krb5_data_free(&rspac);
     krb5_free_keyblock_contents(context, &sessionkey);
     if(krbtgt_out)
        _kdc_free_ent(context, krbtgt_out);
@@ -2060,6 +2074,9 @@ out:
 
     free_EncTicketPart(&adtkt);
 
+    if (mspac)
+       krb5_pac_free(context, mspac);
+
     return ret;
 }
 
@@ -2080,6 +2097,7 @@ _kdc_tgs_rep(krb5_context context,
     krb5_error_code ret;
     int i = 0;
     const PA_DATA *tgs_req;
+    Key *header_key = NULL;
 
     hdb_entry_ex *krbtgt = NULL;
     krb5_ticket *ticket = NULL;
@@ -2117,6 +2135,7 @@ _kdc_tgs_rep(krb5_context context,
                            &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() */
@@ -2134,6 +2153,7 @@ _kdc_tgs_rep(krb5_context context,
                          &req->req_body,
                          krbtgt,
                          krbtgt_etype,
+                         header_key,
                          replykey,
                          rk_is_subkey,
                          ticket,
index fb1c8a6a993f11555f08b9b8b8b0bfec30603e04..43dc89e2bc010233f114312fbe071b5d0f76f440 100644 (file)
@@ -77,8 +77,14 @@ _kdc_pac_generate(krb5_context context,
                  krb5_pac *pac)
 {
     *pac = NULL;
-    if (windcft == NULL)
+    if (krb5_config_get_bool_default(context, NULL, FALSE, "realms",
+                                    client->entry.principal->realm,
+                                    "disable_pac", NULL))
        return 0;
+    if (windcft == NULL) {
+       return krb5_pac_init(context, pac);
+    }
+
     if (windcft->pac_pk_generate != NULL && pk_reply_key != NULL)
        return (windcft->pac_pk_generate)(windcctx, context,
                                          client, pk_reply_key, pac);
@@ -92,20 +98,17 @@ _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)
 {
     krb5_error_code ret;
 
     if (windcft == NULL)
-       return 0;
+       return KRB5_PLUGIN_NO_HANDLE;
 
     ret = windcft->pac_verify(windcctx, context,
                              client_principal,
                              delegated_proxy_principal,
                              client, server, krbtgt, pac);
-    if (ret == 0)
-       *verified = 1;
     return ret;
 }
 
index bf90826cb06c3d3cf7ac36801066447c7ce2478b..dda258da3d11090e59359062fa7cdd6949250605 100644 (file)
@@ -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/source4/heimdal/lib/krb5/authdata.c b/source4/heimdal/lib/krb5/authdata.c
new file mode 100644 (file)
index 0000000..ac42661
--- /dev/null
@@ -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;
+}
index 26aae107b3269df1ecda1c5e4d8e36b184665d25..eec1e84c7bd51148678792150a30656df6f09eb4 100644 (file)
@@ -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 {                                                    \
@@ -142,13 +145,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;
     }
@@ -191,7 +194,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) {
@@ -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){
            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;
        }
 
@@ -213,7 +216,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];
@@ -221,7 +224,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];
@@ -229,10 +232,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];
        }
     }
 
@@ -425,6 +436,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);
 }
@@ -444,6 +456,7 @@ verify_checksum(krb5_context context,
     uint32_t type;
     krb5_error_code ret;
     Checksum cksum;
+    size_t cksumsize;
 
     memset(&cksum, 0, sizeof(cksum));
 
@@ -456,8 +469,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);
@@ -804,7 +826,6 @@ out:
     return ret;
 }
 
-
 /**
  * Verify the PAC.
  *
@@ -844,18 +865,22 @@ 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;
 
@@ -897,6 +922,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;
@@ -965,13 +1004,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;
@@ -985,9 +1025,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) {
@@ -995,9 +1035,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) {
@@ -1005,9 +1045,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 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 have two logon names", ""));
+                                      N_("PAC has multiple ticket checksums", ""));
                goto out;
            }
        }
@@ -1019,6 +1069,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;
@@ -1044,6 +1096,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 */
@@ -1055,6 +1112,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;
@@ -1095,10 +1153,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 {
@@ -1156,6 +1228,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);
@@ -1171,6 +1253,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;
 
@@ -1187,3 +1295,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);
+}
index b95ba92f4f6fc3840877b1a3af18a1678e7e1283..e04e02aace944ada6ac5da15534900604437cf96 100644 (file)
@@ -751,6 +751,11 @@ HEIMDAL_KRB5_2.0 {
                _krb5_get_host_realm_int;
                _krb5_get_int;
                _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;
index 09c525c29574f648efebcb84a2bea6b9ac47ec5a..39e3f5d56e801eca95706f24b1bc5c345864fe6f 100644 (file)
@@ -617,7 +617,7 @@ if not bld.CONFIG_SET("USING_SYSTEM_KRB5"):
     KRB5_SOURCE = [os.path.join('lib/krb5/', x) for x in to_list(
                                    '''acache.c add_et_list.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
                                    changepw.c codec.c config_file.c
                                    constants.c convert_creds.c
index 5d3c4ac4bfd1b29825f9f15fd780a5fe27178108..c2a6256029f3e36f5b3a8b7b850ec00ac60004c3 100755 (executable)
@@ -810,7 +810,7 @@ planoldpythontestsuite("ad_dc:local", "samba.tests.gpo", extra_args=['-U"$USERNA
 planoldpythontestsuite("ad_dc:local", "samba.tests.dckeytab", extra_args=['-U"$USERNAME%$PASSWORD"'])
 
 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("ad_dc_default", "samba.tests.krb5.simple_tests",
                        environ={'SERVICE_USERNAME':'$SERVER',