]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
negotiate_kerberos_auth: Support Kerberos PAC-ResourceGroups (#1597)
authorankor2023 <138755079+ankor2023@users.noreply.github.com>
Tue, 5 Mar 2024 11:13:19 +0000 (11:13 +0000)
committerSquid Anubis <squid-anubis@squid-cache.org>
Tue, 5 Mar 2024 12:10:36 +0000 (12:10 +0000)
Parse the ResourceGroupIds pac-data structure to have information
about the user's membership in AD Domain Local groups.

Previously, the helper obtained user groups information only from
GroupIds and ExtraSids pac-data structures (of the
KERB_VALIDATION_INFO structure).
The patch extends the functionality of the helper.
Now it additionally parse the ResourceGroupIds pac-data structure
where Domain Local AD-group rids are located.
It appends these groups to the the list generated by parsing
GroupIds and ExtraSids.
No changes in existing helper deployments are required.

The new parsing functions are similar to those already used for
parsing GroupIds and ExtraSids.

src/auth/negotiate/kerberos/negotiate_kerberos_pac.cc

index 6ffb943cdd5c2519066a68167b00a5f23bc7268e..494024f8e70829bdbfa3de4cc604f473de1c4d83 100644 (file)
@@ -42,6 +42,9 @@
 
 #if HAVE_GSSAPI && HAVE_PAC_SUPPORT
 
+#define LOGON_EXTRA_SIDS 0x0020
+#define LOGON_RESOURCE_GROUPS 0x0200
+
 static int bpos;
 static krb5_data *ad_data;
 static unsigned char *p;
@@ -362,6 +365,134 @@ getextrasids(char *ad_groups, uint32_t ExtraSids, uint32_t SidCount)
     return ad_groups;
 }
 
+static char *
+get_resource_group_domain_sid(const uint32_t ResourceGroupDomainSid, size_t &length)
+{
+    if (ResourceGroupDomainSid != 0) {
+        uint8_t rev;
+        uint64_t idauth;
+        char dli[256];
+
+        align(4);
+
+        // ResourceGroupDomainSid structure:
+        //  4 bytes nauth
+        //  1 byte revision  = 1
+        //  1 byte nsub (it is equal to the number of dashes minus two)
+        //  6 bytes idauth   (for NT Authority it is 5)
+        //  4 bytes sauth1
+        //  ... nauth timss
+        //  4 bytes sauthN
+
+        uint32_t nauth = get4byt();
+
+        // check if nauth math will produce invalid length values on 32-bit
+        static uint32_t maxGidCount = (UINT32_MAX - 4 - 1 - 1 - 6)/4;
+        if (nauth > maxGidCount) {
+            debug((char *) "%s| %s: ERROR: Too many subAuths in the ResourceGroupDomainSID: nauth = %d > %d\n",
+                  LogTime(), PROGRAM, nauth, maxGidCount);
+            return nullptr;
+        }
+
+        // length = revision[1byte]+nsub[1byte]+idauth[6bytes]+nauth*sauth[4bytes]
+        length = 1 + 1 + 6 + nauth*4;
+
+        auto sid = static_cast<char *>(xcalloc(length, 1));
+        // 1 byte revision
+        // 1 byte nsub
+        // 6 bytes+nauth*4bytes idauth+sauths
+        memcpy((void *)&sid[0], (const void*)&p[bpos], 1);
+        memcpy((void *)&sid[1], (const void*)&p[bpos+1], 1);
+        sid[1]++;  // ++ as it will be used in a rid concatenation
+        memcpy((void *)&sid[2], (const void*)&p[bpos+2], 6 + nauth*4);
+
+        /* mainly for debug only */
+        rev = get1byt();
+        bpos = bpos + 1; /* nsub */
+        idauth = get6byt_be();
+
+        int rv = snprintf(dli, sizeof(dli), "S-%d-%lu", rev, (long unsigned int)idauth);
+        assert(rv > 0);
+        for (int l=0; l<(int)nauth; l++) {
+            uint32_t sauth;
+            sauth = get4byt();
+            rv = snprintf((char *)&dli[strlen(dli)], sizeof(dli) - strlen(dli), "-%u", sauth);
+            assert(rv > 0);
+        }
+        debug((char *) "%s| %s: INFO: Got ResourceGroupDomainSid %s\n", LogTime(), PROGRAM, dli);
+        return sid;
+    }
+
+    length = 0;
+    return nullptr;
+}
+
+static bool
+get_resource_groups(char *ad_groups, uint32_t ResourceGroupDomainSid,  uint32_t ResourceGroupIds, uint32_t ResourceGroupCount)
+{
+    if (!ad_groups) {
+        debug((char *) "%s| %s: ERR: No space to store resource groups\n",
+              LogTime(), PROGRAM);
+        return false;
+    }
+
+    size_t group_domain_sid_len = 0;
+    const auto resource_group_domain_sid = get_resource_group_domain_sid(ResourceGroupDomainSid, group_domain_sid_len);
+    if (!resource_group_domain_sid)
+        return false;
+
+    if (ResourceGroupIds != 0) {
+        align(4);
+        uint32_t ngroup = get4byt();
+        if (ngroup != ResourceGroupCount) {
+            debug((char *) "%s| %s: ERROR: Group encoding error => ResourceGroupCount: %d != Array size: %d\n",
+                  LogTime(), PROGRAM, ResourceGroupCount, ngroup);
+            xfree(resource_group_domain_sid);
+            return false;
+        }
+        debug((char *) "%s| %s: INFO: Found %d Resource Group rids\n", LogTime(), PROGRAM, ResourceGroupCount);
+
+        // prepare a group template which begins with the resource_group_domain_sid
+        size_t length = group_domain_sid_len + 4;  // +4 for a rid concatenation
+        auto *st = static_cast<char *>(xcalloc(length, 1));
+
+        memcpy((void *)st, (const void*)resource_group_domain_sid, group_domain_sid_len); // template
+
+        for (int l=0; l < (int)ResourceGroupCount; l++) {
+            uint32_t sauth;
+            memcpy((void *)&st[group_domain_sid_len], (const void*)&p[bpos], 4);  // rid concatenation
+
+            if (!pstrcat(ad_groups, " group=")) {
+                debug((char *) "%s| %s: WARN: Too many groups ! size > %d : %s\n",
+                      LogTime(), PROGRAM, MAX_PAC_GROUP_SIZE, ad_groups);
+            }
+
+            struct base64_encode_ctx ctx;
+            base64_encode_init(&ctx);
+            const uint32_t expectedSz = base64_encode_len(length) + 1 /* terminator */;
+            char *b64buf = static_cast<char *>(xcalloc(expectedSz, 1));
+            size_t blen = base64_encode_update(&ctx, b64buf, length, reinterpret_cast<uint8_t*>(st));
+            blen += base64_encode_final(&ctx, b64buf + blen);
+            b64buf[expectedSz - 1] = '\0';
+            if (!pstrcat(ad_groups, reinterpret_cast<char*>(b64buf))) {
+                debug((char *) "%s| %s: WARN: Too many groups ! size > %d : %s\n",
+                      LogTime(), PROGRAM, MAX_PAC_GROUP_SIZE, ad_groups);
+            }
+            xfree(b64buf);
+
+            sauth = get4byt();
+            debug((char *) "%s| %s: Info: Got rid: %u\n", LogTime(), PROGRAM, sauth);
+            /* attribute */
+            bpos = bpos + 4;
+        }
+
+        xfree(st);
+    }
+
+    xfree(resource_group_domain_sid);
+    return true;
+}
+
 char *
 get_ad_groups(char *ad_groups, krb5_context context, krb5_pac pac)
 {
@@ -378,12 +509,11 @@ get_ad_groups(char *ad_groups, krb5_context context, krb5_pac pac)
     uint32_t GroupIds=0;
     uint32_t LogonDomainId=0;
     uint32_t SidCount=0;
+    uint32_t UserFlags=0;
     uint32_t ExtraSids=0;
-    /*
     uint32_t ResourceGroupDomainSid=0;
     uint32_t ResourceGroupCount=0;
     uint32_t ResourceGroupIds=0;
-    */
     char **Rids=nullptr;
     int l=0;
 
@@ -436,10 +566,9 @@ get_ad_groups(char *ad_groups, krb5_context context, krb5_pac pac)
     bpos = bpos+12;
     GroupCount = get4byt();
     GroupIds = get4byt();
-    /* 4 bytes UserFlags
-     * 16 bytes UserSessionKey
-     */
-    bpos = bpos+20;
+    UserFlags = get4byt();
+    /// 16 bytes UserSessionKey
+    bpos = bpos+16;
     getustr(&LogonServer);
     getustr(&LogonDomainName);
     LogonDomainId = get4byt();
@@ -454,11 +583,11 @@ get_ad_groups(char *ad_groups, krb5_context context, krb5_pac pac)
     bpos = bpos+40;
     SidCount = get4byt();
     ExtraSids = get4byt();
-    /* 4 bytes ResourceGroupDomainSid
-     * 4 bytes ResourceGroupCount
-     * 4 bytes ResourceGroupIds
-     */
-    bpos = bpos+12;
+
+    ResourceGroupDomainSid = get4byt();
+    ResourceGroupCount = get4byt();
+    ResourceGroupIds = get4byt();
+
     /*
      * Read all data from structure => Now check pointers
      */
@@ -480,10 +609,24 @@ get_ad_groups(char *ad_groups, krb5_context context, krb5_pac pac)
     if (checkustr(&LogonDomainName)<0)
         goto k5clean;
     ad_groups = getdomaingids(ad_groups,LogonDomainId,Rids,GroupCount);
-    if ((ad_groups = getextrasids(ad_groups,ExtraSids,SidCount))==nullptr)
-        goto k5clean;
+
+    // https://learn.microsoft.com/en-us/previous-versions/aa302203(v=msdn.10)?redirectedfrom=MSDN#top-level-pac-structure
+    if ((UserFlags&LOGON_EXTRA_SIDS) != 0) {
+        // EXTRA_SIDS structures are present and valid
+        debug((char *) "%s| %s: Info: EXTRA_SIDS are present\n", LogTime(), PROGRAM);
+        if ((ad_groups = getextrasids(ad_groups,ExtraSids,SidCount)) == nullptr)
+            goto k5clean;
+    }
+
+    if ((UserFlags&LOGON_RESOURCE_GROUPS) != 0 && ResourceGroupDomainSid && ResourceGroupIds && ResourceGroupCount) {
+        // RESOURCE_GROUPS structures are present and valid
+        debug((char *) "%s| %s: Info: RESOURCE_GROUPS are present\n", LogTime(), PROGRAM);
+        if (!get_resource_groups(ad_groups, ResourceGroupDomainSid, ResourceGroupIds, ResourceGroupCount))
+            goto k5clean;
+    }
 
     debug((char *) "%s| %s: INFO: Read %d of %d bytes \n", LogTime(), PROGRAM, bpos, (int)ad_data->length);
+
     if (Rids) {
         for ( l=0; l<(int)GroupCount; l++) {
             xfree(Rids[l]);
@@ -492,6 +635,7 @@ get_ad_groups(char *ad_groups, krb5_context context, krb5_pac pac)
     }
     krb5_free_data(context, ad_data);
     return ad_groups;
+
 k5clean:
     if (Rids) {
         for ( l=0; l<(int)GroupCount; l++) {