]> git.ipfire.org Git - thirdparty/krb5.git/commitdiff
Add PAC ticket signature APIs
authorIsaac Boukris <iboukris@gmail.com>
Fri, 7 Jan 2022 18:46:24 +0000 (13:46 -0500)
committerGreg Hudson <ghudson@mit.edu>
Wed, 12 Jan 2022 18:28:07 +0000 (13:28 -0500)
Microsoft added a third PAC signature over the ticket to prevent
servers from setting the forwardable flag on evidence tickets.  Add
new APIs to generate and verify ticket signatures, as well as defines
for this and other new PAC buffer types.  Deprecate the old signing
functions as they cannot generate ticket signatures.  Modify several
error returns to better match the protocol errors generated by Active
Directory.

[ghudson@mit.edu: adjusted contracts for KDC requirements; simplified
and commented code changes; wrote commit message.  rharwood@redhat.com
also did some work on this commit.]

ticket: 9043 (new)

doc/appdev/refs/api/index.rst
doc/appdev/refs/macros/index.rst
src/include/krb5/krb5.hin
src/lib/krb5/krb/deps
src/lib/krb5/krb/int-proto.h
src/lib/krb5/krb/pac.c
src/lib/krb5/krb/pac_sign.c
src/lib/krb5/krb/t_pac.c
src/lib/krb5/libkrb5.exports
src/lib/krb5_32.def
src/plugins/kdb/test/kdb_test.c

index 9e03fd386f6fa4d867410ed842c7a7b22b6df391..d12be47c3ce124ea26ad29b3187308fcb9aa36b6 100644 (file)
@@ -223,6 +223,8 @@ Rarely used public interfaces
    krb5_init_creds_step.rst
    krb5_init_keyblock.rst
    krb5_is_referral_realm.rst
+   krb5_kdc_sign_ticket.rst
+   krb5_kdc_verify_ticket.rst
    krb5_kt_add_entry.rst
    krb5_kt_end_seq_get.rst
    krb5_kt_get_entry.rst
index 722ebbb98e07fb2ee84ba63fd6b02067de930eec..a0d4f26701e17641252c5d68ff831220ea636662 100644 (file)
@@ -235,12 +235,18 @@ Public
    KRB5_NT_UNKNOWN.rst
    KRB5_NT_WELLKNOWN.rst
    KRB5_NT_X500_PRINCIPAL.rst
+   KRB5_PAC_ATTRIBUTES_INFO.rst
    KRB5_PAC_CLIENT_INFO.rst
+   KRB5_PAC_CLIENT_CLAIMS.rst
    KRB5_PAC_CREDENTIALS_INFO.rst
    KRB5_PAC_DELEGATION_INFO.rst
+   KRB5_PAC_DEVICE_CLAIMS.rst
+   KRB5_PAC_DEVICE_INFO.rst
    KRB5_PAC_LOGON_INFO.rst
    KRB5_PAC_PRIVSVR_CHECKSUM.rst
+   KRB5_PAC_REQUESTOR.rst
    KRB5_PAC_SERVER_CHECKSUM.rst
+   KRB5_PAC_TICKET_CHECKSUM.rst
    KRB5_PAC_UPN_DNS_INFO.rst
    KRB5_PADATA_AFS3_SALT.rst
    KRB5_PADATA_AP_REQ.rst
index 8b4e98d7a7daaebd6d182bde934034dc1b81322a..c0194c3c947e2e8209dd450e28872a990df90e2b 100644 (file)
@@ -1875,7 +1875,7 @@ krb5_verify_checksum(krb5_context context, krb5_cksumtype ctype,
 #define KRB5_AUTHDATA_CAMMAC    96
 #define KRB5_AUTHDATA_WIN2K_PAC 128
 #define KRB5_AUTHDATA_ETYPE_NEGOTIATION 129     /**< RFC 4537 */
-#define KRB5_AUTHDATA_SIGNTICKET        512     /**< formerly 142 in krb5 1.8 */
+#define KRB5_AUTHDATA_SIGNTICKET        512     /**< @deprecated use PAC */
 #define KRB5_AUTHDATA_FX_ARMOR 71
 #define KRB5_AUTHDATA_AUTH_INDICATOR 97
 #define KRB5_AUTHDATA_AP_OPTIONS 143
@@ -8152,6 +8152,12 @@ krb5_verify_authdata_kdc_issued(krb5_context context,
 #define KRB5_PAC_CLIENT_INFO       10 /**< Client name and ticket info */
 #define KRB5_PAC_DELEGATION_INFO   11 /**< Constrained delegation info */
 #define KRB5_PAC_UPN_DNS_INFO      12 /**< User principal name and DNS info */
+#define KRB5_PAC_CLIENT_CLAIMS     13 /**< Client claims information */
+#define KRB5_PAC_DEVICE_INFO       14 /**< Device information */
+#define KRB5_PAC_DEVICE_CLAIMS     15 /**< Device claims information */
+#define KRB5_PAC_TICKET_CHECKSUM   16 /**< Ticket checksum */
+#define KRB5_PAC_ATTRIBUTES_INFO   17 /**< PAC attributes */
+#define KRB5_PAC_REQUESTOR         18 /**< PAC requestor SID */
 
 struct krb5_pac_data;
 /** PAC data structure to convey authorization information */
@@ -8309,56 +8315,84 @@ krb5_pac_verify_ext(krb5_context context, const krb5_pac pac,
                     krb5_boolean with_realm);
 
 /**
- * Sign a PAC.
+ * Verify a PAC, possibly including ticket signature
  *
- * @param [in]  context         Library context
- * @param [in]  pac             PAC handle
- * @param [in]  authtime        Expected timestamp
- * @param [in]  principal       Expected principal name (or NULL)
- * @param [in]  server_key      Key for server checksum
- * @param [in]  privsvr_key     Key for KDC checksum
- * @param [out] data            Signed PAC encoding
+ * @param [in] context          Library context
+ * @param [in] enc_tkt          Ticket enc-part, possibly containing a PAC
+ * @param [in] server_princ     Canonicalized name of ticket server
+ * @param [in] server           Key to validate server checksum (or NULL)
+ * @param [in] privsvr          Key to validate KDC checksum (or NULL)
+ * @param [out] pac_out         Verified PAC (NULL if no PAC included)
  *
- * This function signs @a pac using the keys @a server_key and @a privsvr_key
- * and returns the signed encoding in @a data.  @a pac is modified to include
- * the server and KDC checksum buffers.  Use krb5_free_data_contents() to free
- * @a data when it is no longer needed.
+ * If a PAC is present in @a enc_tkt, verify its signatures.  If @a privsvr is
+ * not NULL and @a server_princ is not a krbtgt or kadmin/changepw service,
+ * require a ticket signature over @a enc_tkt in addition to the KDC signature.
+ * Place the verified PAC in @a pac_out.  If an invalid PAC signature is found,
+ * return an error matching the Windows KDC protocol code for that condition as
+ * closely as possible.
  *
- * @version New in 1.10
+ * If no PAC is present in @a enc_tkt, set @a pac_out to NULL and return
+ * successfully.
+ *
+ * @note This function does not validate the PAC_CLIENT_INFO buffer.  If a
+ * specific value is expected, the caller can make a separate call to
+ * krb5_pac_verify_ext() with a principal but no keys.
+ *
+ * @retval 0 Success; otherwise - Kerberos error codes
+ *
+ * @version New in 1.20
  */
 krb5_error_code KRB5_CALLCONV
+krb5_kdc_verify_ticket(krb5_context context, const krb5_enc_tkt_part *enc_tkt,
+                       krb5_const_principal server_princ,
+                       const krb5_keyblock *server,
+                       const krb5_keyblock *privsvr, krb5_pac *pac_out);
+
+/** @deprecated Use krb5_kdc_sign_ticket() instead. */
+krb5_error_code KRB5_CALLCONV
 krb5_pac_sign(krb5_context context, krb5_pac pac, krb5_timestamp authtime,
               krb5_const_principal principal, const krb5_keyblock *server_key,
               const krb5_keyblock *privsvr_key, krb5_data *data);
 
+/** @deprecated Use krb5_kdc_sign_ticket() instead. */
+krb5_error_code KRB5_CALLCONV
+krb5_pac_sign_ext(krb5_context context, krb5_pac pac, krb5_timestamp authtime,
+                  krb5_const_principal principal,
+                  const krb5_keyblock *server_key,
+                  const krb5_keyblock *privsvr_key, krb5_boolean with_realm,
+                  krb5_data *data);
+
 /**
- * Sign a PAC, possibly with a specified realm.
+ * Sign a PAC, possibly including a ticket signature
  *
  * @param [in]  context         Library context
+ * @param [in]  enc_tkt         The ticket for the signature
  * @param [in]  pac             PAC handle
- * @param [in]  authtime        Expected timestamp
- * @param [in]  principal       Principal name (or NULL)
- * @param [in]  server_key      Key for server checksum
- * @param [in]  privsvr_key     Key for KDC checksum
+ * @param [in]  server_princ    Canonical ticket server name
+ * @param [in]  client_princ    PAC_CLIENT_INFO principal (or NULL)
+ * @param [in]  server          Key for server checksum
+ * @param [in]  privsvr         Key for KDC and ticket checksum
  * @param [in]  with_realm      If true, include the realm of @a principal
- * @param [out] data            Signed PAC encoding
  *
- * This function is similar to krb5_pac_sign(), but adds a parameter
- * @a with_realm.  If @a with_realm is true, the PAC_CLIENT_INFO field of the
- * signed PAC will include the realm of @a principal as well as the name.  This
- * flag is necessary to generate PACs for cross-realm S4U2Self referrals.
+ * Sign @a pac using the keys @a server and @a privsvr.  Include a ticket
+ * signature over @a enc_tkt if @a server_princ is not a TGS or kadmin/changepw
+ * principal name.  Add the signed PAC's encoding to the authorization data of
+ * @a enc_tkt in the first slot, wrapped in an AD-IF-RELEVANT container.  If @a
+ * client_princ is non-null, add a PAC_CLIENT_INFO buffer, including the realm
+ * if @a with_realm is true.
  *
- * @version New in 1.17
+ * @retval 0 on success, otherwise - Kerberos error codes
+ *
+ * @version New in 1.20
  */
 krb5_error_code KRB5_CALLCONV
-krb5_pac_sign_ext(krb5_context context, krb5_pac pac, krb5_timestamp authtime,
-                  krb5_const_principal principal,
-                  const krb5_keyblock *server_key,
-                  const krb5_keyblock *privsvr_key, krb5_boolean with_realm,
-                  krb5_data *data);
+krb5_kdc_sign_ticket(krb5_context context, krb5_enc_tkt_part *enc_tkt,
+                     const krb5_pac pac, krb5_const_principal server_princ,
+                     krb5_const_principal client_princ,
+                     const krb5_keyblock *server, const krb5_keyblock *privsvr,
+                     krb5_boolean with_realm);
 
-
-/*
+/**
  * Read client information from a PAC.
  *
  * @param [in]  context         Library context
index 6197a9839e022e6f03514fda858c4348b27f5bbf..ca6ab25faa0fcb0f77ada7c5cc0c18df86322082 100644 (file)
@@ -710,7 +710,7 @@ pac.so pac.po $(OUTPRE)pac.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
   $(top_srcdir)/include/k5-utf8.h $(top_srcdir)/include/krb5.h \
   $(top_srcdir)/include/krb5/authdata_plugin.h $(top_srcdir)/include/krb5/plugin.h \
   $(top_srcdir)/include/port-sockets.h $(top_srcdir)/include/socket-utils.h \
-  authdata.h pac.c
+  authdata.h int-proto.h pac.c
 pac_sign.so pac_sign.po $(OUTPRE)pac_sign.$(OBJEXT): \
   $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \
   $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \
@@ -721,7 +721,8 @@ pac_sign.so pac_sign.po $(OUTPRE)pac_sign.$(OBJEXT): \
   $(top_srcdir)/include/k5-trace.h $(top_srcdir)/include/k5-utf8.h \
   $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h \
   $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h \
-  $(top_srcdir)/include/socket-utils.h authdata.h pac_sign.c
+  $(top_srcdir)/include/socket-utils.h authdata.h int-proto.h \
+  pac_sign.c
 padata.so padata.po $(OUTPRE)padata.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \
   $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \
   $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h \
index 15f4adf28541e7ed9c3425053fd0b139ea94dfad..b62f9049f0186a63965dfe677ac3c552713b7ef0 100644 (file)
@@ -395,4 +395,7 @@ k5_sname_wildcard_host(krb5_context context, krb5_const_principal mprinc);
 krb5_int32
 k5_infer_principal_type(krb5_principal princ);
 
+krb5_boolean
+k5_pac_should_have_ticket_signature(krb5_const_principal sprinc);
+
 #endif /* KRB5_INT_FUNC_PROTO__ */
index 5118bf7017420ced5db20eeb15c9562e052eb5cd..ab1606d9e2b4fad4b5422c61dc21e7759033593a 100644 (file)
@@ -25,6 +25,7 @@
  */
 
 #include "k5-int.h"
+#include "int-proto.h"
 #include "authdata.h"
 
 /* draft-brezak-win2k-krb-authz-00 */
@@ -537,8 +538,10 @@ k5_pac_verify_server_checksum(krb5_context context,
     checksum.checksum_type = load_32_le(p);
     checksum.length = checksum_data.length - PAC_SIGNATURE_DATA_LENGTH;
     checksum.contents = p + PAC_SIGNATURE_DATA_LENGTH;
+    if (checksum.checksum_type == CKSUMTYPE_SHA1)
+        return KRB5KDC_ERR_SUMTYPE_NOSUPP;
     if (!krb5_c_is_keyed_cksum(checksum.checksum_type))
-        return KRB5KRB_AP_ERR_INAPP_CKSUM;
+        return KRB5KRB_ERR_GENERIC;
 
     pac_data.length = pac->data.length;
     pac_data.data = k5memdup(pac->data.data, pac->data.length, &ret);
@@ -571,7 +574,7 @@ k5_pac_verify_server_checksum(krb5_context context,
     }
 
     if (valid == FALSE)
-        ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
+        ret = KRB5KRB_AP_ERR_MODIFIED;
 
     return ret;
 }
@@ -607,7 +610,7 @@ k5_pac_verify_kdc_checksum(krb5_context context,
     p = (krb5_octet *)privsvr_checksum.data;
     checksum.checksum_type = load_32_le(p);
     if (!krb5_c_is_keyed_cksum(checksum.checksum_type))
-        return KRB5KRB_AP_ERR_INAPP_CKSUM;
+        return KRB5KRB_ERR_GENERIC;
 
     /* There may be an RODCIdentifier trailer (see [MS-PAC] 2.8), so look up
      * the length of the checksum by its type. */
@@ -629,11 +632,148 @@ k5_pac_verify_kdc_checksum(krb5_context context,
         return ret;
 
     if (valid == FALSE)
-        ret = KRB5KRB_AP_ERR_BAD_INTEGRITY;
+        ret = KRB5KRB_AP_ERR_MODIFIED;
 
     return ret;
 }
 
+static krb5_error_code
+verify_ticket_checksum(krb5_context context, const krb5_pac pac,
+                       const krb5_data *ticket, const krb5_keyblock *privsvr)
+{
+    krb5_error_code ret;
+    krb5_checksum checksum;
+    krb5_data checksum_data;
+    krb5_boolean valid;
+    krb5_octet *p;
+
+    ret = k5_pac_locate_buffer(context, pac, KRB5_PAC_TICKET_CHECKSUM,
+                               &checksum_data);
+    if (ret != 0)
+        return KRB5KRB_AP_ERR_MODIFIED;
+
+    if (checksum_data.length < PAC_SIGNATURE_DATA_LENGTH)
+        return KRB5_BAD_MSIZE;
+
+    p = (krb5_octet *)checksum_data.data;
+    checksum.checksum_type = load_32_le(p);
+    checksum.length = checksum_data.length - PAC_SIGNATURE_DATA_LENGTH;
+    checksum.contents = p + PAC_SIGNATURE_DATA_LENGTH;
+    if (!krb5_c_is_keyed_cksum(checksum.checksum_type))
+        return KRB5KRB_ERR_GENERIC;
+
+    ret = krb5_c_verify_checksum(context, privsvr,
+                                 KRB5_KEYUSAGE_APP_DATA_CKSUM, ticket,
+                                 &checksum, &valid);
+    if (ret != 0)
+        return ret;
+
+    return valid ? 0 : KRB5KRB_AP_ERR_MODIFIED;
+}
+
+/* Per MS-PAC 2.8.3, tickets encrypted to TGS and password change principals
+ * should not have ticket signatures. */
+krb5_boolean
+k5_pac_should_have_ticket_signature(krb5_const_principal sprinc)
+{
+    if (IS_TGS_PRINC(sprinc))
+        return FALSE;
+    if (sprinc->length == 2 && data_eq_string(sprinc->data[0], "kadmin") &&
+        data_eq_string(sprinc->data[1], "changepw"))
+        return FALSE;
+    return TRUE;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_kdc_verify_ticket(krb5_context context, const krb5_enc_tkt_part *enc_tkt,
+                       krb5_const_principal server_princ,
+                       const krb5_keyblock *server,
+                       const krb5_keyblock *privsvr, krb5_pac *pac_out)
+{
+    krb5_error_code ret;
+    krb5_pac pac = NULL;
+    krb5_data *recoded_tkt = NULL;
+    krb5_authdata **authdata, *orig, **ifrel = NULL, **recoded_ifrel = NULL;
+    uint8_t z = 0;
+    krb5_authdata zpac = { KV5M_AUTHDATA, KRB5_AUTHDATA_WIN2K_PAC, 1, &z };
+    size_t i, j;
+
+    *pac_out = NULL;
+
+    /*
+     * Find the position of the PAC in the ticket authdata.  ifrel will be the
+     * decoded AD-IF-RELEVANT container at position i containing a PAC, and j
+     * will be the offset within the container.
+     */
+    authdata = enc_tkt->authorization_data;
+    for (i = 0; authdata != NULL && authdata[i] != NULL; i++) {
+        if (authdata[i]->ad_type != KRB5_AUTHDATA_IF_RELEVANT)
+            continue;
+
+        ret = krb5_decode_authdata_container(context,
+                                             KRB5_AUTHDATA_IF_RELEVANT,
+                                             authdata[i], &ifrel);
+        if (ret)
+            goto cleanup;
+
+        for (j = 0; ifrel[j] != NULL; j++) {
+            if (ifrel[j]->ad_type == KRB5_AUTHDATA_WIN2K_PAC)
+                break;
+        }
+        if (ifrel[j] != NULL)
+            break;
+
+        krb5_free_authdata(context, ifrel);
+        ifrel = NULL;
+    }
+
+    /* Stop and return successfully if we didn't find a PAC. */
+    if (ifrel == NULL) {
+        ret = 0;
+        goto cleanup;
+    }
+
+    ret = krb5_pac_parse(context, ifrel[j]->contents, ifrel[j]->length, &pac);
+    if (ret)
+        goto cleanup;
+
+    if (privsvr != NULL && k5_pac_should_have_ticket_signature(server_princ)) {
+        /* To check the PAC ticket signatures, re-encode the ticket with the
+         * PAC contents replaced by a single zero. */
+        orig = ifrel[j];
+        ifrel[j] = &zpac;
+        ret = krb5_encode_authdata_container(context,
+                                             KRB5_AUTHDATA_IF_RELEVANT,
+                                             ifrel, &recoded_ifrel);
+        ifrel[j] = orig;
+        if (ret)
+            goto cleanup;
+        orig = authdata[i];
+        authdata[i] = recoded_ifrel[0];
+        ret = encode_krb5_enc_tkt_part(enc_tkt, &recoded_tkt);
+        authdata[i] = orig;
+        if (ret)
+            goto cleanup;
+
+        ret = verify_ticket_checksum(context, pac, recoded_tkt, privsvr);
+        if (ret)
+            goto cleanup;
+    }
+
+    ret = krb5_pac_verify_ext(context, pac, enc_tkt->times.authtime, NULL,
+                              server, privsvr, FALSE);
+
+    *pac_out = pac;
+    pac = NULL;
+
+cleanup:
+    krb5_pac_free(context, pac);
+    krb5_free_data(context, recoded_tkt);
+    krb5_free_authdata(context, ifrel);
+    krb5_free_authdata(context, recoded_ifrel);
+    return ret;
+}
+
 krb5_error_code KRB5_CALLCONV
 krb5_pac_verify(krb5_context context,
                 const krb5_pac pac,
index 12f0259b4f0af813c31b0cf7a09bb350158cc032..0f9581abbbd9453a344a95d36b5dbbeb597d6707 100644 (file)
@@ -25,6 +25,7 @@
  */
 
 #include "k5-int.h"
+#include "int-proto.h"
 #include "authdata.h"
 
 /* draft-brezak-win2k-krb-authz-00 */
@@ -286,3 +287,123 @@ krb5_pac_sign_ext(krb5_context context, krb5_pac pac, krb5_timestamp authtime,
 
     return 0;
 }
+
+/* Add a signature over der_enc_tkt in privsvr to pac.  der_enc_tkt should be
+ * encoded with a dummy PAC authdata element containing a single zero byte. */
+static krb5_error_code
+add_ticket_signature(krb5_context context, const krb5_pac pac,
+                     krb5_data *der_enc_tkt, const krb5_keyblock *privsvr)
+{
+    krb5_error_code ret;
+    krb5_data ticket_cksum;
+    krb5_cksumtype ticket_cksumtype;
+    krb5_crypto_iov iov[2];
+
+    /* Create zeroed buffer for checksum. */
+    ret = k5_insert_checksum(context, pac, KRB5_PAC_TICKET_CHECKSUM,
+                             privsvr, &ticket_cksumtype);
+    if (ret)
+        return ret;
+
+    ret = k5_pac_locate_buffer(context, pac, KRB5_PAC_TICKET_CHECKSUM,
+                               &ticket_cksum);
+    if (ret)
+        return ret;
+
+    iov[0].flags = KRB5_CRYPTO_TYPE_DATA;
+    iov[0].data = *der_enc_tkt;
+    iov[1].flags = KRB5_CRYPTO_TYPE_CHECKSUM;
+    iov[1].data = make_data(ticket_cksum.data + PAC_SIGNATURE_DATA_LENGTH,
+                            ticket_cksum.length - PAC_SIGNATURE_DATA_LENGTH);
+    ret = krb5_c_make_checksum_iov(context, ticket_cksumtype, privsvr,
+                                   KRB5_KEYUSAGE_APP_DATA_CKSUM, iov, 2);
+    if (ret)
+        return ret;
+
+    store_32_le(ticket_cksumtype, ticket_cksum.data);
+    return 0;
+}
+
+/* Set *out to an AD-IF-RELEVANT authdata element containing a PAC authdata
+ * element with contents pac_data. */
+static krb5_error_code
+encode_pac_ad(krb5_context context, krb5_data *pac_data, krb5_authdata **out)
+{
+    krb5_error_code ret;
+    krb5_authdata *container[2], **encoded_container = NULL;
+    krb5_authdata pac_ad = { KV5M_AUTHDATA, KRB5_AUTHDATA_WIN2K_PAC };
+    uint8_t z = 0;
+
+    pac_ad.contents = (pac_data != NULL) ? (uint8_t *)pac_data->data : &z;
+    pac_ad.length = (pac_data != NULL) ? pac_data->length : 1;
+    container[0] = &pac_ad;
+    container[1] = NULL;
+
+    ret = krb5_encode_authdata_container(context, KRB5_AUTHDATA_IF_RELEVANT,
+                                         container, &encoded_container);
+    if (ret)
+        return ret;
+
+    *out = encoded_container[0];
+    free(encoded_container);
+    return 0;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_kdc_sign_ticket(krb5_context context, krb5_enc_tkt_part *enc_tkt,
+                     const krb5_pac pac, krb5_const_principal server_princ,
+                     krb5_const_principal client_princ,
+                     const krb5_keyblock *server, const krb5_keyblock *privsvr,
+                     krb5_boolean with_realm)
+{
+    krb5_error_code ret;
+    krb5_data *der_enc_tkt = NULL, pac_data = empty_data();
+    krb5_authdata **list, *pac_ad;
+    size_t count;
+
+    /* Reallocate space for another authdata element in enc_tkt. */
+    list = enc_tkt->authorization_data;
+    for (count = 0; list != NULL && list[count] != NULL; count++);
+    list = realloc(enc_tkt->authorization_data, (count + 2) * sizeof(*list));
+    if (list == NULL)
+        return ENOMEM;
+    list[count] = NULL;
+    enc_tkt->authorization_data = list;
+
+    /* Create a dummy PAC for ticket signing and make it the first element. */
+    ret = encode_pac_ad(context, NULL, &pac_ad);
+    if (ret)
+        goto cleanup;
+    memmove(list + 1, list, (count + 1) * sizeof(*list));
+    list[0] = pac_ad;
+
+    if (k5_pac_should_have_ticket_signature(server_princ)) {
+        ret = encode_krb5_enc_tkt_part(enc_tkt, &der_enc_tkt);
+        if (ret)
+            goto cleanup;
+
+        assert(privsvr != NULL);
+        ret = add_ticket_signature(context, pac, der_enc_tkt, privsvr);
+        if (ret)
+            goto cleanup;
+    }
+
+    ret = krb5_pac_sign_ext(context, pac, enc_tkt->times.authtime,
+                            client_princ, server, privsvr, with_realm,
+                            &pac_data);
+    if (ret)
+        goto cleanup;
+
+    /* Replace the dummy PAC with the signed real one. */
+    ret = encode_pac_ad(context, &pac_data, &pac_ad);
+    if (ret)
+        goto cleanup;
+    free(list[0]->contents);
+    free(list[0]);
+    list[0] = pac_ad;
+
+cleanup:
+    krb5_free_data(context, der_enc_tkt);
+    krb5_free_data_contents(context, &pac_data);
+    return ret;
+}
index ee47152ee44d7df6bc72a88d2e0f1934439c8be9..0b1b1f05648b33c0bf08bc9fa95261ddb49f1d42 100644 (file)
@@ -595,6 +595,186 @@ check_pac(krb5_context context, int index, const unsigned char *pdata,
     krb5_pac_free(context, pac);
 }
 
+static const krb5_keyblock ticket_sig_krbtgt_key = {
+    0, ENCTYPE_AES256_CTS_HMAC_SHA1_96,
+    32, U("\x7a\x58\x98\xd2\xaf\xa6\xaf\xc0\x6a\xce\x06\x04\x4b\xc2\x70\x84"
+          "\x9b\x8e\x0a\x6c\x4c\x07\xdc\x6f\xbb\x48\x43\xe1\xd2\xaa\x97\xf7")
+};
+
+static const krb5_keyblock ticket_sig_server_key = {
+    0, ENCTYPE_ARCFOUR_HMAC,
+    16, U("\xed\x23\x11\x20\x7a\x21\x44\x20\xbf\xc0\x8d\x36\xf7\xf6\xb2\x3e")
+};
+
+static const krb5_data ticket_data = {
+    .length = 972, .data =
+    "\x61\x82\x03\xC8\x30\x82\x03\xC4\xA0\x03\x02\x01\x05\xA1\x0A\x1B"
+    "\x08\x43\x44\x4F\x4D\x2E\x43\x4F\x4D\xA2\x0F\x30\x0D\xA0\x03\x02"
+    "\x01\x01\xA1\x06\x30\x04\x1B\x02\x73\x31\xA3\x82\x03\x9E\x30\x82"
+    "\x03\x9A\xA0\x03\x02\x01\x17\xA1\x03\x02\x01\x03\xA2\x82\x03\x8C"
+    "\x04\x82\x03\x88\x44\x31\x61\x20\x17\xC9\xFE\xBC\xAC\x46\xB5\x77"
+    "\xE9\x68\x04\x4C\x9B\x31\x91\x0C\xC1\xD4\xDD\xEF\xC7\x34\x20\x08"
+    "\x90\x91\xE8\x79\xE0\xB5\x03\x26\xA4\x65\xDE\xEC\x47\x03\x2A\x8F"
+    "\x61\xE7\x4D\x38\x5A\x42\x95\x5A\xF9\x2F\x41\x2C\x2A\x6E\x60\xA1"
+    "\xEB\x51\xB3\xBD\x4C\x00\x41\x2A\x44\x76\x08\x37\x1A\x51\xFD\x65"
+    "\x67\x7E\xBF\x3D\x90\x86\xE3\x9A\x54\x6B\x67\xA8\x08\x7A\x73\xCC"
+    "\xC3\xB7\x4B\xD5\x5C\x3A\x14\x6C\xC1\x5F\x54\x4B\x92\x55\xB4\xB7"
+    "\x92\x23\x3F\x53\x89\x47\x8E\x1F\x8B\xB9\xDB\x3B\x93\xE8\x70\xE4"
+    "\x24\xB8\x9D\xF0\x0E\x35\x28\xF8\x7A\x27\x5D\xF7\x25\x97\x9C\xF5"
+    "\x9F\x9F\x64\x04\xF2\xA3\xAB\x11\x15\xB6\xDA\x18\xD6\x46\xD5\xE6"
+    "\xB8\x08\xDE\x0A\x62\xFD\xF8\xAA\x52\x90\xD9\x67\x29\xB2\xCD\x06"
+    "\xB6\xB0\x50\x2B\x3F\x0F\xA3\xA5\xBF\xAA\x6E\x40\x03\xD6\x5F\x02"
+    "\xBC\xD8\x18\x47\x97\x09\xD7\xE4\x96\x3B\xCB\xEB\x92\x2C\x3C\x49"
+    "\xFF\x1F\x71\xE0\x52\x94\x0F\x8B\x9F\xB8\x2A\xBB\x9C\xE2\xA3\xDD"
+    "\x38\x89\xE2\xB1\x0B\x9E\x1F\x7A\xB3\xE3\xD2\xB0\x94\xDC\x87\xBE"
+    "\x37\xA6\xD3\xB3\x29\x35\x9A\x72\xC3\x7A\xF1\xA9\xE6\xC5\xD1\x26"
+    "\x83\x65\x44\x17\xBA\x55\xA8\x5E\x94\x26\xED\xE9\x8A\x93\x11\x5D"
+    "\x7E\x20\x1B\x9C\x15\x9E\x13\x37\x03\x4D\xDD\x99\x51\xD8\x66\x29"
+    "\x6A\xB9\xFB\x49\xFE\x52\x78\xDA\x86\x85\xA9\xA3\xB9\xEF\xEC\xAD"
+    "\x35\xA6\x8D\xAC\x0F\x75\x22\xBB\x0B\x49\x1C\x13\x52\x40\xC9\x52"
+    "\x69\x09\x54\xD1\x0F\x94\x3F\x22\x48\x67\xB0\x96\x28\xAA\xE6\x28"
+    "\xD9\x0C\x08\xEF\x51\xED\x15\x5E\xA2\x53\x59\xA5\x03\xB4\x06\x20"
+    "\x3D\xCC\xB4\xC5\xF8\x8C\x73\x67\xA3\x21\x3D\x19\xCD\xD4\x12\x28"
+    "\xD2\x93\xDE\x0D\xF0\x71\x10\x50\xD6\x33\x35\x04\x11\x64\x43\x39"
+    "\xC3\xDF\x96\xE3\x66\xE3\x85\xCA\xE7\x67\x14\x3A\xF0\x43\xAA\xBB"
+    "\xD4\x1D\xB5\x24\xB5\x74\x90\x25\xA7\x87\x7E\xDB\xD3\x83\x8A\x3A"
+    "\x69\xA8\x2D\xAF\xB7\xB8\xF3\xDC\x13\xAF\x45\x61\x3F\x59\x39\x7E"
+    "\x69\xDE\x0C\x04\xF1\x10\x6B\xB4\x56\xFA\x21\x9F\x72\x2B\x60\x86"
+    "\xE3\x23\x0E\xC4\x51\xF6\xBE\xD8\xE1\x5F\xEE\x73\x4C\x17\x4C\x2C"
+    "\x1B\xFB\x9F\x1F\x7A\x3B\x07\x5B\x8E\xF1\x01\xAC\xD6\x30\x94\x8A"
+    "\x5D\x22\x6F\x08\xCE\xED\x5E\xB6\xDB\x86\x8C\x87\xEB\x8D\x91\xFF"
+    "\x0A\x86\x30\xBD\xC0\xF8\x25\xE7\xAE\x24\x35\xF2\xFC\xE5\xFD\x1B"
+    "\xB0\x05\x4A\xA3\xE5\xEB\x2E\x05\xAD\x99\x67\x49\x87\xE6\xB3\x87"
+    "\x82\xA4\x59\xA7\x6E\xDD\xF2\xB6\x66\xE8\xF7\x70\xF5\xBD\xC9\x0E"
+    "\xFA\x9C\x79\x84\xD4\x9B\x05\x0E\xBB\xF5\xDB\xEF\xFC\xCC\x26\xF2"
+    "\x93\xCF\xD2\x04\x3C\xA9\x2C\x65\x42\x97\x86\xD8\x38\x0A\x1E\xF6"
+    "\xD6\xCA\x30\xB5\x1A\xEC\xFB\xBA\x3B\x84\x57\xB0\xFD\xFB\xE6\xBC"
+    "\xF2\x76\xF6\x4C\xBB\xAB\xB1\x31\xA1\x27\x7C\xE6\xE6\x81\xB6\xCE"
+    "\x84\x86\x40\xB6\x40\x33\xC4\xF8\xB4\x15\xCF\xAA\xA5\x51\x78\xB9"
+    "\x8B\x50\x25\xB2\x88\x86\x96\x72\x8C\x71\x4D\xB5\x3A\x94\x86\x77"
+    "\x0E\x95\x9B\x16\x93\xEF\x3A\x11\x79\xBA\x83\xF7\x74\xD3\x8D\xBA"
+    "\x15\xE1\x2C\x04\x57\xA8\x92\x1E\x9D\x00\x8E\x20\xFD\x30\x70\xE7"
+    "\xF5\x65\x2F\x19\x0C\x94\xBA\x03\x71\x12\x96\xCD\xC8\xB4\x96\xDB"
+    "\xCE\x19\xC2\xDF\x3C\xC2\xF6\x3D\x53\xED\x98\xA5\x41\x72\x2A\x22"
+    "\x7B\xF3\x2B\x17\x6C\xE1\x39\x7D\xAE\x9B\x11\xF9\xC1\xA6\x9E\x9F"
+    "\x89\x3C\x12\xAA\x94\x74\xA7\x4F\x70\xE8\xB9\xDE\x04\xF0\x9D\x39"
+    "\x24\x2D\x92\xE8\x46\x2D\x2E\xF0\x40\x66\x1A\xD9\x27\xF9\x98\xF1"
+    "\x81\x1D\x70\x62\x63\x30\x6D\xCD\x84\x04\x5F\xFA\x83\xD3\xEC\x8D"
+    "\x86\xFB\x40\x61\xC1\x8A\x45\xFF\x7B\xD9\xD4\x18\x61\x7F\x51\xE3"
+    "\xFC\x1E\x18\xF0\xAF\xC6\x18\x2C\xE1\x6D\x5D\xF9\x62\xFC\x20\xA3"
+    "\xB2\x8A\x5F\xE5\xBB\x29\x0F\x99\x63\x07\x88\x38\x3A\x3B\x73\x2A"
+    "\x6D\xDA\x3D\xA8\x0D\x8F\x56\x41\x89\x82\xE5\xB8\x61\x00\x64\x7D"
+    "\x17\x0C\xCE\x03\x55\x8F\xF4\x5B\x0D\x50\xF2\xEB\x05\x67\xBE\xDB"
+    "\x7B\x75\xC5\xEA\xA1\xAB\x1D\xB0\x3C\x6D\x42\x08\x0B\x9A\x45\x20"
+    "\xA8\x8F\xE5\x67\x47\x30\xDE\x93\x5F\x43\x05\xEB\xA8\x2D\x80\xF5"
+    "\x1A\xB8\x4A\x4E\x42\x2D\x0B\x7A\xDC\x46\x20\x2D\x13\x17\xDD\x4B"
+    "\x94\x96\xAA\x1F\x06\x0C\x1F\x62\x07\x9C\x40\xA1"
+};
+
+static void
+test_pac_ticket_signature(krb5_context context)
+{
+    krb5_error_code ret;
+    krb5_ticket *ticket;
+    krb5_principal sprinc;
+    krb5_authdata **authdata1, **authdata2;
+    krb5_pac pac, pac2, pac3;
+    uint32_t *list;
+    size_t len, i;
+    krb5_data data;
+
+    ret = krb5_decode_ticket(&ticket_data, &ticket);
+    if (ret)
+        err(context, ret, "while decoding ticket");
+
+    ret = krb5_decrypt_tkt_part(context, &ticket_sig_server_key, ticket);
+    if (ret)
+        err(context, ret, "while decrypting ticket");
+
+    ret = krb5_parse_name(context, "s1@CDOM.COM", &sprinc);
+    if (ret)
+        err(context, ret, "krb5_parse_name");
+
+    ret = krb5_kdc_verify_ticket(context, ticket->enc_part2, sprinc,
+                                 &ticket_sig_server_key,
+                                 &ticket_sig_krbtgt_key, &pac);
+    if (ret)
+        err(context, ret, "while verifying ticket");
+
+    /* In this test, the server is also the client. */
+    ret = krb5_pac_verify(context, pac, ticket->enc_part2->times.authtime,
+                          ticket->server, NULL, NULL);
+    if (ret)
+        err(context, ret, "while verifying PAC client info");
+
+    /* We know there is only a PAC in this test's ticket. */
+    authdata1 = ticket->enc_part2->authorization_data;
+    ticket->enc_part2->authorization_data = NULL;
+
+    ret = krb5_kdc_sign_ticket(context, ticket->enc_part2, pac, sprinc,
+                               sprinc, &ticket_sig_server_key,
+                               &ticket_sig_krbtgt_key, FALSE);
+    if (ret)
+        err(context, ret, "while signing ticket");
+
+    authdata2 = ticket->enc_part2->authorization_data;
+    assert(authdata2 != NULL);
+    assert(authdata2[1] == NULL);
+
+    assert(authdata1[0]->length == authdata2[0]->length);
+    assert(memcmp(authdata1[0]->contents, authdata2[0]->contents,
+                  authdata1[0]->length) == 0);
+
+    /* Test adding signatures to a new PAC. */
+    ret = krb5_pac_init(context, &pac2);
+    if (ret)
+        err(context, ret, "krb5_pac_init");
+
+    ret = krb5_pac_get_types(context, pac, &len, &list);
+    if (ret)
+        err(context, ret, "krb5_pac_get_types");
+
+    for (i = 0; i < len; i++) {
+        /* Skip server_cksum, privsvr_cksum, and ticket_cksum. */
+        if (list[i] == 6 || list[i] == 7 || list[i] == 16)
+            continue;
+
+        ret = krb5_pac_get_buffer(context, pac, list[i], &data);
+        if (ret)
+            err(context, ret, "krb5_pac_get_buffer");
+
+        ret = krb5_pac_add_buffer(context, pac2, list[i], &data);
+        if (ret)
+            err(context, ret, "krb5_pac_add_buffer");
+
+        krb5_free_data_contents(context, &data);
+    }
+    free(list);
+
+    krb5_free_authdata(context, authdata1);
+    krb5_free_authdata(context, ticket->enc_part2->authorization_data);
+    ticket->enc_part2->authorization_data = NULL;
+
+    ret = krb5_kdc_sign_ticket(context, ticket->enc_part2, pac2, sprinc, NULL,
+                               &ticket_sig_server_key, &ticket_sig_krbtgt_key,
+                               FALSE);
+    if (ret)
+        err(context, ret, "while signing ticket");
+
+    /* We can't compare the data since the order of the buffers may differ. */
+    ret = krb5_kdc_verify_ticket(context, ticket->enc_part2, sprinc,
+                                 &ticket_sig_server_key,
+                                 &ticket_sig_krbtgt_key, &pac3);
+    if (ret)
+        err(context, ret, "while verifying ticket");
+
+    krb5_pac_free(context, pac);
+    krb5_pac_free(context, pac2);
+    krb5_pac_free(context, pac3);
+    krb5_free_principal(context, sprinc);
+    krb5_free_ticket(context, ticket);
+}
+
 int
 main(int argc, char **argv)
 {
@@ -608,6 +788,8 @@ main(int argc, char **argv)
     if (ret)
         err(NULL, 0, "krb5_init_contex");
 
+    test_pac_ticket_signature(context);
+
     ret = krb5_set_default_realm(context, "WIN2K3.THINKER.LOCAL");
     if (ret)
         err(context, ret, "krb5_set_default_realm");
index 0a05521d5a7601391bd700da2d5a2b311619ca8b..9a7a16f68d5b0303674b20891f6e89474aad2c14 100644 (file)
@@ -465,6 +465,8 @@ krb5_is_permitted_enctype
 krb5_is_referral_realm
 krb5_is_thread_safe
 krb5_kdc_rep_decrypt_proc
+krb5_kdc_sign_ticket
+krb5_kdc_verify_ticket
 krb5_kt_add_entry
 krb5_kt_client_default
 krb5_kt_close
index cf690dbe42100fe0a820c81e9fbbc6f6e49b6cea..b1610974b1877e79d78eb01a97df4ede91ed06fb 100644 (file)
@@ -508,3 +508,5 @@ EXPORTS
        krb5_marshal_credentials                        @472
        krb5_unmarshal_credentials                      @473
        k5_sname_compare                                @474 ; PRIVATE GSSAPI
+       krb5_kdc_sign_ticket                            @475 ;
+       krb5_kdc_verify_ticket                          @476 ;
index 2e02e2141f5eec308449b7ca2d4ef7dfd43b38c1..495bec42adfacc453dc8b276f1633b5a0b789c81 100644 (file)
@@ -700,7 +700,7 @@ verify_kdc_signature(krb5_context context, krb5_pac pac,
     int tries;
 
     ret = krb5_pac_verify(context, pac, 0, NULL, NULL, tgt_key);
-    if (ret != KRB5KRB_AP_ERR_BAD_INTEGRITY)
+    if (ret != KRB5KRB_AP_ERR_MODIFIED)
         return ret;
 
     kvno = tgt->key_data[0].key_data_kvno - 1;
@@ -709,7 +709,7 @@ verify_kdc_signature(krb5_context context, krb5_pac pac,
     for (tries = 2; tries > 0 && kvno > 0; tries--, kvno--) {
         ret = krb5_dbe_find_enctype(context, tgt, -1, -1, kvno, &kd);
         if (ret)
-            return KRB5KRB_AP_ERR_BAD_INTEGRITY;
+            return KRB5KRB_AP_ERR_MODIFIED;
         ret = krb5_dbe_decrypt_key_data(context, NULL, kd, &old_key, NULL);
         if (ret)
             return ret;
@@ -722,7 +722,7 @@ verify_kdc_signature(krb5_context context, krb5_pac pac,
         kvno = kd->key_data_kvno - 1;
     }
 
-    return KRB5KRB_AP_ERR_BAD_INTEGRITY;
+    return KRB5KRB_AP_ERR_MODIFIED;
 }
 
 static krb5_error_code