krb5 commit: Add PAC ticket signature APIs

Greg Hudson ghudson at mit.edu
Wed Jan 12 14:38:31 EST 2022


https://github.com/krb5/krb5/commit/ee4e3c5c9eee061048d5b7393b8f3820d1a563a8
commit ee4e3c5c9eee061048d5b7393b8f3820d1a563a8
Author: Isaac Boukris <iboukris at gmail.com>
Date:   Fri Jan 7 13:46:24 2022 -0500

    Add PAC ticket signature APIs
    
    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 at mit.edu: adjusted contracts for KDC requirements; simplified
    and commented code changes; wrote commit message.  rharwood at redhat.com
    also did some work on this commit.]
    
    ticket: 9043 (new)

 doc/appdev/refs/api/index.rst    |    2 +
 doc/appdev/refs/macros/index.rst |    6 ++
 src/include/krb5/krb5.hin        |   98 ++++++++++++++-------
 src/lib/krb5/krb/deps            |    5 +-
 src/lib/krb5/krb/int-proto.h     |    3 +
 src/lib/krb5/krb/pac.c           |  148 ++++++++++++++++++++++++++++++-
 src/lib/krb5/krb/pac_sign.c      |  121 +++++++++++++++++++++++++
 src/lib/krb5/krb/t_pac.c         |  182 ++++++++++++++++++++++++++++++++++++++
 src/lib/krb5/libkrb5.exports     |    2 +
 src/lib/krb5_32.def              |    2 +
 src/plugins/kdb/test/kdb_test.c  |    6 +-
 11 files changed, 534 insertions(+), 41 deletions(-)

diff --git a/doc/appdev/refs/api/index.rst b/doc/appdev/refs/api/index.rst
index 9e03fd3..d12be47 100644
--- a/doc/appdev/refs/api/index.rst
+++ b/doc/appdev/refs/api/index.rst
@@ -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
diff --git a/doc/appdev/refs/macros/index.rst b/doc/appdev/refs/macros/index.rst
index 722ebbb..a0d4f26 100644
--- a/doc/appdev/refs/macros/index.rst
+++ b/doc/appdev/refs/macros/index.rst
@@ -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
diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin
index 8b4e98d..c0194c3 100644
--- a/src/include/krb5/krb5.hin
+++ b/src/include/krb5/krb5.hin
@@ -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
diff --git a/src/lib/krb5/krb/deps b/src/lib/krb5/krb/deps
index 6197a98..ca6ab25 100644
--- a/src/lib/krb5/krb/deps
+++ b/src/lib/krb5/krb/deps
@@ -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 \
diff --git a/src/lib/krb5/krb/int-proto.h b/src/lib/krb5/krb/int-proto.h
index 15f4adf..b62f904 100644
--- a/src/lib/krb5/krb/int-proto.h
+++ b/src/lib/krb5/krb/int-proto.h
@@ -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__ */
diff --git a/src/lib/krb5/krb/pac.c b/src/lib/krb5/krb/pac.c
index 5118bf7..ab1606d 100644
--- a/src/lib/krb5/krb/pac.c
+++ b/src/lib/krb5/krb/pac.c
@@ -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,
diff --git a/src/lib/krb5/krb/pac_sign.c b/src/lib/krb5/krb/pac_sign.c
index 12f0259..0f9581a 100644
--- a/src/lib/krb5/krb/pac_sign.c
+++ b/src/lib/krb5/krb/pac_sign.c
@@ -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;
+}
diff --git a/src/lib/krb5/krb/t_pac.c b/src/lib/krb5/krb/t_pac.c
index ee47152..0b1b1f0 100644
--- a/src/lib/krb5/krb/t_pac.c
+++ b/src/lib/krb5/krb/t_pac.c
@@ -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 at 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");
diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports
index 0a05521..9a7a16f 100644
--- a/src/lib/krb5/libkrb5.exports
+++ b/src/lib/krb5/libkrb5.exports
@@ -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
diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def
index cf690db..b161097 100644
--- a/src/lib/krb5_32.def
+++ b/src/lib/krb5_32.def
@@ -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 ;
diff --git a/src/plugins/kdb/test/kdb_test.c b/src/plugins/kdb/test/kdb_test.c
index 2e02e21..495bec4 100644
--- a/src/plugins/kdb/test/kdb_test.c
+++ b/src/plugins/kdb/test/kdb_test.c
@@ -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


More information about the cvs-krb5 mailing list