krb5 commit: Add PAC APIs which can include a client realm

Greg Hudson <[email protected]>
Newsgroups gmane.comp.encryption.kerberos.cvs
Message-ID <[email protected]>
https://github.com/krb5/krb5/commit/3b56f54e31ee8db2b15a059e0d53609c1f4c3b83
commit 3b56f54e31ee8db2b15a059e0d53609c1f4c3b83
Author: Isaac Boukris <[email protected]>
Date:   Sat Sep 29 07:21:56 2018 +0300

    Add PAC APIs which can include a client realm
    
    These APIs are needed for KDC handling of cross-realm S4U2Self
    tickets; see [MS-SFU] 3.2.5.x.  Note that we currently do not allow
    re-signing a PAC to include the realm; the caller must create a new
    one.
    
    [[email protected]: added documentation; changed names and parameter
    order; edited commit message]
    
    ticket: 8749 (new)

 doc/appdev/refs/api/index.rst |    2 +
 src/include/krb5/krb5.hin     |   50 +++++++++++++++
 src/lib/krb5/krb/authdata.h   |    3 +-
 src/lib/krb5/krb/pac.c        |   31 ++++++++--
 src/lib/krb5/krb/pac_sign.c   |   32 ++++++++--
 src/lib/krb5/krb/t_pac.c      |  132 +++++++++++++++++++++++++++++++++++++++++
 src/lib/krb5/libkrb5.exports  |    2 +
 src/lib/krb5_32.def           |    2 +
 8 files changed, 242 insertions(+), 12 deletions(-)

diff --git a/doc/appdev/refs/api/index.rst b/doc/appdev/refs/api/index.rst
index 66aff59..f8a5aa5 100644
--- a/doc/appdev/refs/api/index.rst
+++ b/doc/appdev/refs/api/index.rst
@@ -256,7 +256,9 @@ Rarely used public interfaces
    krb5_pac_init.rst
    krb5_pac_parse.rst
    krb5_pac_sign.rst
+   krb5_pac_sign_ext.rst
    krb5_pac_verify.rst
+   krb5_pac_verify_ext.rst
    krb5_prepend_error_message.rst
    krb5_principal2salt.rst
    krb5_rd_cred.rst
diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin
index 21fabb4..c40a6cc 100644
--- a/src/include/krb5/krb5.hin
+++ b/src/include/krb5/krb5.hin
@@ -8313,6 +8313,30 @@ krb5_pac_verify(krb5_context context, const krb5_pac pac,
                 const krb5_keyblock *server, const krb5_keyblock *privsvr);
 
 /**
+ * Verify a PAC, possibly from a specified realm.
+ *
+ * @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 to validate server checksum (or NULL)
+ * @param [in] privsvr          Key to validate KDC checksum (or NULL)
+ * @param [in] with_realm       If true, expect the realm of @a principal
+ *
+ * This function is similar to krb5_pac_verify(), but adds a parameter
+ * @a with_realm.  If @a with_realm is true, the PAC_CLIENT_INFO field is
+ * expected to include the realm of @a principal as well as the name.  This
+ * flag is necessary to verify PACs in cross-realm S4U2Self referral TGTs.
+ *
+ * @version New in 1.17
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_pac_verify_ext(krb5_context context, const krb5_pac pac,
+                    krb5_timestamp authtime, krb5_const_principal principal,
+                    const krb5_keyblock *server, const krb5_keyblock *privsvr,
+                    krb5_boolean with_realm);
+
+/**
  * Sign a PAC.
  *
  * @param [in]  context         Library context
@@ -8336,6 +8360,32 @@ krb5_pac_sign(krb5_context context, krb5_pac pac, krb5_timestamp authtime,
               const krb5_keyblock *privsvr_key, krb5_data *data);
 
 /**
+ * Sign a PAC, possibly with a specified realm.
+ *
+ * @param [in]  context         Library context
+ * @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]  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.
+ *
+ * @version New in 1.17
+ */
+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);
+
+/**
  * Allow the appplication to override the profile's allow_weak_crypto setting.
  *
  * @param [in] context          Library context
diff --git a/src/lib/krb5/krb/authdata.h b/src/lib/krb5/krb/authdata.h
index 1e5c084..74d663c 100644
--- a/src/lib/krb5/krb/authdata.h
+++ b/src/lib/krb5/krb/authdata.h
@@ -90,7 +90,8 @@ krb5_error_code
 k5_pac_validate_client(krb5_context context,
                        const krb5_pac pac,
                        krb5_timestamp authtime,
-                       krb5_const_principal principal);
+                       krb5_const_principal principal,
+                       krb5_boolean with_realm);
 
 krb5_error_code
 k5_pac_add_buffer(krb5_context context,
diff --git a/src/lib/krb5/krb/pac.c b/src/lib/krb5/krb/pac.c
index c9b5de3..cc74f37 100644
--- a/src/lib/krb5/krb/pac.c
+++ b/src/lib/krb5/krb/pac.c
@@ -403,7 +403,8 @@ krb5_error_code
 k5_pac_validate_client(krb5_context context,
                        const krb5_pac pac,
                        krb5_timestamp authtime,
-                       krb5_const_principal principal)
+                       krb5_const_principal principal,
+                       krb5_boolean with_realm)
 {
     krb5_error_code ret;
     krb5_data client_info;
@@ -413,7 +414,7 @@ k5_pac_validate_client(krb5_context context,
     krb5_ui_2 pac_princname_length;
     int64_t pac_nt_authtime;
     krb5_principal pac_principal;
-    int flags;
+    int flags = 0;
 
     ret = k5_pac_locate_buffer(context, pac, KRB5_PAC_CLIENT_INFO,
                                &client_info);
@@ -442,10 +443,15 @@ k5_pac_validate_client(krb5_context context,
         return ret;
 
     /* Parse the UTF-8 name as an enterprise principal if we are matching
-     * against one; otherwise parse it as a regular principal with no realm. */
-    flags = KRB5_PRINCIPAL_PARSE_NO_REALM;
+     * against one; otherwise parse it as a regular principal. */
     if (principal->type == KRB5_NT_ENTERPRISE_PRINCIPAL)
         flags |= KRB5_PRINCIPAL_PARSE_ENTERPRISE;
+
+    if (with_realm)
+        flags |= KRB5_PRINCIPAL_PARSE_REQUIRE_REALM;
+    else
+        flags |= KRB5_PRINCIPAL_PARSE_NO_REALM;
+
     ret = krb5_parse_name_flags(context, pac_princname, flags, &pac_principal);
     if (ret != 0) {
         free(pac_princname);
@@ -458,6 +464,7 @@ k5_pac_validate_client(krb5_context context,
         !krb5_principal_compare_flags(context,
                                       pac_principal,
                                       principal,
+                                      with_realm ? 0 :
                                       KRB5_PRINCIPAL_COMPARE_IGNORE_REALM))
         ret = KRB5KRB_AP_WRONG_PRINC;
 
@@ -623,6 +630,19 @@ krb5_pac_verify(krb5_context context,
                 const krb5_keyblock *server,
                 const krb5_keyblock *privsvr)
 {
+    return krb5_pac_verify_ext(context, pac, authtime, principal, server,
+                               privsvr, FALSE);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_pac_verify_ext(krb5_context context,
+                    const krb5_pac pac,
+                    krb5_timestamp authtime,
+                    krb5_const_principal principal,
+                    const krb5_keyblock *server,
+                    const krb5_keyblock *privsvr,
+                    krb5_boolean with_realm)
+{
     krb5_error_code ret;
 
     if (server != NULL) {
@@ -638,7 +658,8 @@ krb5_pac_verify(krb5_context context,
     }
 
     if (principal != NULL) {
-        ret = k5_pac_validate_client(context, pac, authtime, principal);
+        ret = k5_pac_validate_client(context, pac, authtime,
+                                     principal, with_realm);
         if (ret != 0)
             return ret;
     }
diff --git a/src/lib/krb5/krb/pac_sign.c b/src/lib/krb5/krb/pac_sign.c
index c94899c..12f0259 100644
--- a/src/lib/krb5/krb/pac_sign.c
+++ b/src/lib/krb5/krb/pac_sign.c
@@ -33,7 +33,8 @@ static krb5_error_code
 k5_insert_client_info(krb5_context context,
                       krb5_pac pac,
                       krb5_timestamp authtime,
-                      krb5_const_principal principal)
+                      krb5_const_principal principal,
+                      krb5_boolean with_realm)
 {
     krb5_error_code ret;
     krb5_data client_info;
@@ -41,16 +42,23 @@ k5_insert_client_info(krb5_context context,
     unsigned char *princ_name_utf16 = NULL, *p;
     size_t princ_name_utf16_len = 0;
     uint64_t nt_authtime;
+    int flags = 0;
 
     /* If we already have a CLIENT_INFO buffer, then just validate it */
     if (k5_pac_locate_buffer(context, pac, KRB5_PAC_CLIENT_INFO,
                              &client_info) == 0) {
-        return k5_pac_validate_client(context, pac, authtime, principal);
+        return k5_pac_validate_client(context, pac, authtime, principal,
+                                      with_realm);
     }
 
-    ret = krb5_unparse_name_flags(context, principal,
-                                  KRB5_PRINCIPAL_UNPARSE_NO_REALM,
-                                  &princ_name_utf8);
+    if (!with_realm) {
+        flags |= KRB5_PRINCIPAL_UNPARSE_NO_REALM;
+    } else if (principal->type == KRB5_NT_ENTERPRISE_PRINCIPAL) {
+        /* Avoid quoting the first @ sign for enterprise name with realm. */
+        flags |= KRB5_PRINCIPAL_UNPARSE_DISPLAY;
+    }
+
+    ret = krb5_unparse_name_flags(context, principal, flags, &princ_name_utf8);
     if (ret != 0)
         goto cleanup;
 
@@ -183,6 +191,17 @@ 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)
 {
+    return krb5_pac_sign_ext(context, pac, authtime, principal, server_key,
+                             privsvr_key, FALSE, data);
+}
+
+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_error_code ret;
     krb5_data server_cksum, privsvr_cksum;
     krb5_cksumtype server_cksumtype, privsvr_cksumtype;
@@ -192,7 +211,8 @@ krb5_pac_sign(krb5_context context, krb5_pac pac, krb5_timestamp authtime,
     data->data = NULL;
 
     if (principal != NULL) {
-        ret = k5_insert_client_info(context, pac, authtime, principal);
+        ret = k5_insert_client_info(context, pac, authtime, principal,
+                                    with_realm);
         if (ret != 0)
             return ret;
     }
diff --git a/src/lib/krb5/krb/t_pac.c b/src/lib/krb5/krb/t_pac.c
index 61fb51a..8f9579b 100644
--- a/src/lib/krb5/krb/t_pac.c
+++ b/src/lib/krb5/krb/t_pac.c
@@ -313,6 +313,138 @@ main(int argc, char **argv)
         free(list);
     }
 
+    {
+        krb5_principal ep;
+
+        ret = krb5_parse_name_flags(context, user,
+                                    KRB5_PRINCIPAL_PARSE_ENTERPRISE, &ep);
+        if (ret)
+            err(context, ret, "krb5_parse_name_flags");
+
+        /* Try to verify as enterprise. */
+        ret = krb5_pac_verify(context, pac, authtime, ep, &member_keyblock,
+                              &kdc_keyblock);
+        if (!ret)
+            err(context, ret, "krb5_pac_verify should have failed");
+
+        ret = krb5_pac_sign(context, pac, authtime, ep, &member_keyblock,
+                            &kdc_keyblock, &data);
+        if (!ret)
+            err(context, ret, "krb5_pac_sign should have failed");
+
+        /* Try to verify with realm. */
+        ret = krb5_pac_verify_ext(context, pac, authtime, p, &member_keyblock,
+                                  &kdc_keyblock, TRUE);
+        if (!ret)
+            err(context, ret, "krb5_pac_verify_ext with realm should fail");
+
+        /* Currently we can't re-sign the PAC with realm (although that could
+         * be useful), only sign a new one. */
+        ret = krb5_pac_sign_ext(context, pac, authtime, p, &member_keyblock,
+                                &kdc_keyblock, TRUE, &data);
+        if (!ret)
+            err(context, ret, "krb5_pac_sign_ext with realm should fail");
+
+        krb5_pac_free(context, pac);
+
+        /* Test enterprise. */
+        ret = krb5_pac_init(context, &pac);
+        if (ret)
+            err(context, ret, "krb5_pac_init");
+
+        ret = krb5_pac_sign(context, pac, authtime, ep, &member_keyblock,
+                            &kdc_keyblock, &data);
+        if (ret)
+            err(context, ret, "krb5_pac_sign enterprise failed");
+
+        krb5_pac_free(context, pac);
+
+        ret = krb5_pac_parse(context, data.data, data.length, &pac);
+        krb5_free_data_contents(context, &data);
+        if (ret)
+            err(context, ret, "krb5_pac_parse failed");
+
+        ret = krb5_pac_verify(context, pac, authtime, ep, &member_keyblock,
+                              &kdc_keyblock);
+        if (ret)
+            err(context, ret, "krb5_pac_verify enterprise failed");
+
+        ret = krb5_pac_verify(context, pac, authtime, p, &member_keyblock,
+                              &kdc_keyblock);
+        if (!ret)
+            err(context, ret, "krb5_pac_verify should have failed");
+
+        krb5_pac_free(context, pac);
+
+        /* Test with realm. */
+        ret = krb5_pac_init(context, &pac);
+        if (ret)
+            err(context, ret, "krb5_pac_init");
+
+        ret = krb5_pac_sign_ext(context, pac, authtime, p, &member_keyblock,
+                                &kdc_keyblock, TRUE, &data);
+        if (ret)
+            err(context, ret, "krb5_pac_sign_ext with realm failed");
+
+        krb5_pac_free(context, pac);
+
+        ret = krb5_pac_parse(context, data.data, data.length, &pac);
+        krb5_free_data_contents(context, &data);
+        if (ret)
+            err(context, ret, "krb5_pac_parse failed");
+
+        ret = krb5_pac_verify_ext(context, pac, authtime, p, &member_keyblock,
+                                  &kdc_keyblock, TRUE);
+        if (ret)
+            err(context, ret, "krb5_pac_verify_ext with realm failed");
+
+        ret = krb5_pac_verify(context, pac, authtime, p, &member_keyblock,
+                              &kdc_keyblock);
+        if (!ret)
+            err(context, ret, "krb5_pac_verify should have failed");
+
+        krb5_pac_free(context, pac);
+
+        /* Test enterprise with realm. */
+        ret = krb5_pac_init(context, &pac);
+        if (ret)
+            err(context, ret, "krb5_pac_init");
+
+        ret = krb5_pac_sign_ext(context, pac, authtime, ep, &member_keyblock,
+                                &kdc_keyblock, TRUE, &data);
+        if (ret)
+            err(context, ret, "krb5_pac_sign_ext ent with realm failed");
+
+        krb5_pac_free(context, pac);
+
+        ret = krb5_pac_parse(context, data.data, data.length, &pac);
+        krb5_free_data_contents(context, &data);
+        if (ret)
+            err(context, ret, "krb5_pac_parse failed");
+
+        ret = krb5_pac_verify_ext(context, pac, authtime, ep, &member_keyblock,
+                                  &kdc_keyblock, TRUE);
+        if (ret)
+            err(context, ret, "krb5_pac_verify_ext ent with realm failed");
+
+        ret = krb5_pac_verify(context, pac, authtime, p, &member_keyblock,
+                              &kdc_keyblock);
+        if (!ret)
+            err(context, ret, "krb5_pac_verify should have failed");
+
+        ret = krb5_pac_verify(context, pac, authtime, ep, &member_keyblock,
+                              &kdc_keyblock);
+        if (!ret)
+            err(context, ret, "krb5_pac_verify should have failed");
+
+        ret = krb5_pac_verify_ext(context, pac, authtime, p, &member_keyblock,
+                                  &kdc_keyblock, TRUE);
+        if (!ret)
+            err(context, ret, "krb5_pac_verify_ext should have failed");
+
+        krb5_free_principal(context, ep);
+    }
+
     krb5_pac_free(context, pac);
 
     krb5_free_principal(context, p);
diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports
index 542209d..dfdb72d 100644
--- a/src/lib/krb5/libkrb5.exports
+++ b/src/lib/krb5/libkrb5.exports
@@ -487,7 +487,9 @@ krb5_pac_get_types
 krb5_pac_init
 krb5_pac_parse
 krb5_pac_sign
+krb5_pac_sign_ext
 krb5_pac_verify
+krb5_pac_verify_ext
 krb5_parse_name
 krb5_parse_name_flags
 krb5_prepend_error_message
diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def
index 3bb2a80..c350229 100644
--- a/src/lib/krb5_32.def
+++ b/src/lib/krb5_32.def
@@ -476,6 +476,8 @@ EXPORTS
 
 ; new in 1.17
 	krb5_get_etype_info				@447
+	krb5_pac_sign_ext				@448
+	krb5_pac_verify_ext				@449
 ; private symbols used by SPAKE client module
 	profile_get_string				@439 ; PRIVATE
 	profile_release_string				@440 ; PRIVATE
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.