< Summary

Class:SharpHoundCommonLib.Processors.ACLProcessor
Assembly:SharpHoundCommonLib
File(s):D:\a\SharpHoundCommon\SharpHoundCommon\src\CommonLib\Processors\ACLProcessor.cs
Covered lines:319
Uncovered lines:163
Coverable lines:482
Total lines:677
Line coverage:66.1% (319 of 482)
Covered branches:310
Total branches:403
Branch coverage:76.9% (310 of 403)

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.ctor(...)100%20100%
.cctor()100%10100%
BuildGuidCache()72.72%22085.71%
IsACLProtected(...)0%200%
IsACLProtected(...)100%20100%
ProcessACL(...)0%200%
CalculateInheritanceHash(...)100%1072.72%
GetInheritedAceHashes(...)0%200%
GetInheritedAceHashes()75%16081.25%
ProcessACL()78.15%325062.3%
ProcessGMSAReaders(...)0%200%
ProcessGMSAReaders(...)100%10100%
ProcessGMSAReaders()85.71%28090%

File(s)

D:\a\SharpHoundCommon\SharpHoundCommon\src\CommonLib\Processors\ACLProcessor.cs

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Collections.Generic;
 4using System.DirectoryServices;
 5using System.Security.AccessControl;
 6using System.Security.Cryptography;
 7using System.Security.Principal;
 8using System.Text;
 9using System.Threading.Tasks;
 10using Microsoft.Extensions.Logging;
 11using SharpHoundCommonLib.DirectoryObjects;
 12using SharpHoundCommonLib.Enums;
 13using SharpHoundCommonLib.OutputTypes;
 14
 15namespace SharpHoundCommonLib.Processors {
 16    public class ACLProcessor {
 17        private static readonly Dictionary<Label, string> BaseGuids;
 7918        private readonly ConcurrentDictionary<string, string> _guidMap = new();
 19        private readonly ILogger _log;
 20        private readonly ILdapUtils _utils;
 7921        private readonly ConcurrentHashSet _builtDomainCaches = new(StringComparer.OrdinalIgnoreCase);
 22
 123        static ACLProcessor() {
 24            //Create a dictionary with the base GUIDs of each object type
 125            BaseGuids = new Dictionary<Label, string> {
 126                { Label.User, "bf967aba-0de6-11d0-a285-00aa003049e2" },
 127                { Label.Computer, "bf967a86-0de6-11d0-a285-00aa003049e2" },
 128                { Label.Group, "bf967a9c-0de6-11d0-a285-00aa003049e2" },
 129                { Label.Domain, "19195a5a-6da0-11d0-afd3-00c04fd930c9" },
 130                { Label.GPO, "f30e3bc2-9ff0-11d1-b603-0000f80367c1" },
 131                { Label.OU, "bf967aa5-0de6-11d0-a285-00aa003049e2" },
 132                { Label.Container, "bf967a8b-0de6-11d0-a285-00aa003049e2" },
 133                { Label.Configuration, "bf967a87-0de6-11d0-a285-00aa003049e2" },
 134                { Label.RootCA, "3fdfee50-47f4-11d1-a9c3-0000f80367c1" },
 135                { Label.AIACA, "3fdfee50-47f4-11d1-a9c3-0000f80367c1" },
 136                { Label.EnterpriseCA, "ee4aa692-3bba-11d2-90cc-00c04fd91ab1" },
 137                { Label.NTAuthStore, "3fdfee50-47f4-11d1-a9c3-0000f80367c1" },
 138                { Label.CertTemplate, "e5209ca2-3bba-11d2-90cc-00c04fd91ab1" },
 139                { Label.IssuancePolicy, "37cfd85c-6719-4ad8-8f9e-8678ba627563" }
 140            };
 141        }
 42
 15843        public ACLProcessor(ILdapUtils utils, ILogger log = null) {
 7944            _utils = utils;
 7945            _log = log ?? Logging.LogProvider.CreateLogger("ACLProc");
 7946        }
 47
 48        /// <summary>
 49        ///     Builds a mapping of GUID -> Name for LDAP rights. Used for rights that are created using an extended sch
 50        ///     LAPS
 51        /// </summary>
 2752        private async Task BuildGuidCache(string domain) {
 2753            _log.LogInformation("Building GUID Cache for {Domain}", domain);
 9954            await foreach (var result in _utils.PagedQuery(new LdapQueryParameters {
 2755                               DomainName = domain,
 2756                               LDAPFilter = "(schemaIDGUID=*)",
 2757                               NamingContext = NamingContext.Schema,
 2758                               Attributes = new[] { LDAPProperties.SchemaIDGUID, LDAPProperties.Name },
 3659                           })) {
 1060                if (result.IsSuccess) {
 161                    if (!result.Value.TryGetProperty(LDAPProperties.Name, out var name) ||
 162                        !result.Value.TryGetByteProperty(LDAPProperties.SchemaIDGUID, out var schemaGuid)) {
 063                        continue;
 64                    }
 65
 166                    name = name.ToLower();
 67
 68                    string guid;
 69                    try
 170                    {
 171                        guid = new Guid(schemaGuid).ToString();
 172                    }
 073                    catch
 074                    {
 075                        continue;
 76                    }
 77
 278                    if (name is LDAPProperties.LAPSPlaintextPassword or LDAPProperties.LAPSEncryptedPassword or LDAPProp
 179                        _log.LogInformation("Found GUID for ACL Right {Name}: {Guid} in domain {Domain}", name, guid, do
 180                        _guidMap.TryAdd(guid, name);
 181                    }
 982                } else {
 883                    _log.LogDebug("Error while building GUID cache for {Domain}: {Message}", domain, result.Error);
 884                }
 985            }
 2786        }
 87
 88        /// <summary>
 89        ///     Helper function to use commonlib types in IsACLProtected
 90        /// </summary>
 91        /// <param name="entry"></param>
 92        /// <returns></returns>
 093        public bool IsACLProtected(IDirectoryObject entry) {
 094            if (entry.TryGetByteProperty(LDAPProperties.SecurityDescriptor, out var ntSecurityDescriptor)) {
 095                return IsACLProtected(ntSecurityDescriptor);
 96            }
 97
 098            return false;
 099        }
 100
 101        /// <summary>
 102        ///     Gets the protection state of the access control list
 103        /// </summary>
 104        /// <param name="ntSecurityDescriptor"></param>
 105        /// <returns></returns>
 5106        public bool IsACLProtected(byte[] ntSecurityDescriptor) {
 5107            if (ntSecurityDescriptor == null)
 1108                return false;
 109
 4110            var descriptor = _utils.MakeSecurityDescriptor();
 4111            descriptor.SetSecurityDescriptorBinaryForm(ntSecurityDescriptor);
 112
 4113            return descriptor.AreAccessRulesProtected();
 5114        }
 115
 116        /// <summary>
 117        ///     Helper function to use common lib types and pass appropriate vars to ProcessACL
 118        /// </summary>
 119        /// <param name="result"></param>
 120        /// <param name="searchResult"></param>
 121        /// <returns></returns>
 0122        public IAsyncEnumerable<ACE> ProcessACL(ResolvedSearchResult result, IDirectoryObject searchResult) {
 0123            if (!searchResult.TryGetByteProperty(LDAPProperties.SecurityDescriptor, out var descriptor)) {
 0124                return AsyncEnumerable.Empty<ACE>();
 125            }
 126
 0127            var domain = result.Domain;
 0128            var type = result.ObjectType;
 0129            var hasLaps = searchResult.HasLAPS();
 0130            var name = result.DisplayName;
 131
 0132            return ProcessACL(descriptor, domain, type, hasLaps, name);
 0133        }
 134
 135        internal static string CalculateInheritanceHash(string identityReference, ActiveDirectoryRights rights,
 8136            string aceType, string inheritedObjectType) {
 8137            var hash = identityReference + rights + aceType + inheritedObjectType;
 138            /*
 139             * We're using SHA1 because its fast and this data isn't cryptographically important.
 140             * Additionally, the chances of a collision in our data size is miniscule and irrelevant.
 141             * We cannot use MD5 as it is not FIPS compliant and environments can enforce this setting
 142             */
 143            try
 8144            {
 8145                using (var sha1 = SHA1.Create())
 8146                {
 8147                    var bytes = sha1.ComputeHash(Encoding.UTF8.GetBytes(hash));
 8148                    return BitConverter.ToString(bytes).Replace("-", string.Empty).ToUpper();
 149                }
 150            }
 0151            catch
 0152            {
 0153                return "";
 154            }
 8155        }
 156
 157        /// <summary>
 158        /// Helper function to get inherited ACE hashes using CommonLib types
 159        /// </summary>
 160        /// <param name="directoryObject"></param>
 161        /// <param name="resolvedSearchResult"></param>
 162        /// <returns></returns>
 163        public IEnumerable<string> GetInheritedAceHashes(IDirectoryObject directoryObject,
 0164            ResolvedSearchResult resolvedSearchResult) {
 0165            if (directoryObject.TryGetByteProperty(LDAPProperties.SecurityDescriptor, out var value)) {
 0166                return GetInheritedAceHashes(value, resolvedSearchResult.DisplayName);
 167            }
 168
 0169            return Array.Empty<string>();
 0170        }
 171
 172        /// <summary>
 173        /// Gets the hashes for all aces that are pushing inheritance down the tree for later comparison
 174        /// </summary>
 175        /// <param name="ntSecurityDescriptor"></param>
 176        /// <param name="objectName"></param>
 177        /// <returns></returns>
 2178        public IEnumerable<string> GetInheritedAceHashes(byte[] ntSecurityDescriptor, string objectName = "") {
 3179            if (ntSecurityDescriptor == null) {
 1180                yield break;
 181            }
 182
 1183            _log.LogDebug("Processing Inherited ACE hashes for {Name}", objectName);
 1184            var descriptor = _utils.MakeSecurityDescriptor();
 1185            try {
 1186                descriptor.SetSecurityDescriptorBinaryForm(ntSecurityDescriptor);
 1187            } catch (OverflowException) {
 0188                _log.LogWarning(
 0189                    "Security descriptor on object {Name} exceeds maximum allowable length. Unable to process",
 0190                    objectName);
 0191                yield break;
 192            }
 193
 9194            foreach (var ace in descriptor.GetAccessRules(true, true, typeof(SecurityIdentifier))) {
 195                //Skip all null/deny/inherited aces
 3196                if (ace == null || ace.AccessControlType() == AccessControlType.Deny || ace.IsInherited()) {
 1197                    continue;
 198                }
 199
 1200                var ir = ace.IdentityReference();
 1201                var principalSid = Helpers.PreProcessSID(ir);
 202
 203                //Skip aces for filtered principals
 1204                if (principalSid == null) {
 0205                    continue;
 206                }
 207
 1208                var iFlags = ace.InheritanceFlags;
 1209                if (iFlags == InheritanceFlags.None) {
 0210                    continue;
 211                }
 212
 1213                var aceRights = ace.ActiveDirectoryRights();
 214                //Lowercase this just in case. As far as I know it should always come back that way anyways, but better 
 1215                var aceType = ace.ObjectType().ToString().ToLower();
 1216                var inheritanceType = ace.InheritedObjectType();
 217
 1218                var hash = CalculateInheritanceHash(ir, aceRights, aceType, inheritanceType);
 1219                if (!string.IsNullOrEmpty(hash))
 1220                {
 1221                    yield return hash;
 1222                }
 1223            }
 1224        }
 225
 226        /// <summary>
 227        ///     Read's a raw ntSecurityDescriptor and processes the ACEs in the ACL, filtering out ACEs that
 228        ///     BloodHound is not interested in as well as principals we don't care about
 229        /// </summary>
 230        /// <param name="ntSecurityDescriptor"></param>
 231        /// <param name="objectDomain"></param>
 232        /// <param name="objectName"></param>
 233        /// <param name="objectType"></param>
 234        /// <param name="hasLaps"></param>
 235        /// <returns></returns>
 236        public async IAsyncEnumerable<ACE> ProcessACL(byte[] ntSecurityDescriptor, string objectDomain,
 237            Label objectType,
 27238            bool hasLaps, string objectName = "") {
 54239            if (!_builtDomainCaches.Contains(objectDomain)) {
 27240                _builtDomainCaches.Add(objectDomain);
 27241                await BuildGuidCache(objectDomain);
 27242            }
 243
 28244            if (ntSecurityDescriptor == null) {
 1245                _log.LogDebug("Security Descriptor is null for {Name}", objectName);
 1246                yield break;
 247            }
 248
 26249            var descriptor = _utils.MakeSecurityDescriptor();
 26250            try {
 26251                descriptor.SetSecurityDescriptorBinaryForm(ntSecurityDescriptor);
 26252            } catch (OverflowException) {
 0253                _log.LogWarning(
 0254                    "Security descriptor on object {Name} exceeds maximum allowable length. Unable to process",
 0255                    objectName);
 0256                yield break;
 257            }
 258
 26259            _log.LogDebug("Processing ACL for {ObjectName}", objectName);
 26260            var ownerSid = Helpers.PreProcessSID(descriptor.GetOwner(typeof(SecurityIdentifier)));
 261
 28262            if (ownerSid != null) {
 4263                if (await _utils.ResolveIDAndType(ownerSid, objectDomain) is (true, var resolvedOwner)) {
 2264                    yield return new ACE {
 2265                        PrincipalType = resolvedOwner.ObjectType,
 2266                        PrincipalSID = resolvedOwner.ObjectIdentifier,
 2267                        RightName = EdgeNames.Owns,
 2268                        IsInherited = false,
 2269                        InheritanceHash = ""
 2270                    };
 2271                } else {
 0272                    _log.LogTrace("Failed to resolve owner for {Name}", objectName);
 0273                    yield return new ACE {
 0274                        PrincipalType = Label.Base,
 0275                        PrincipalSID = ownerSid,
 0276                        RightName = EdgeNames.Owns,
 0277                        IsInherited = false,
 0278                        InheritanceHash = ""
 0279                    };
 0280                }
 2281            }
 282
 210283            foreach (var ace in descriptor.GetAccessRules(true, true, typeof(SecurityIdentifier))) {
 52284                if (ace == null || ace.AccessControlType() == AccessControlType.Deny || !ace.IsAceInheritedFrom(BaseGuid
 8285                    continue;
 286                }
 287
 36288                var ir = ace.IdentityReference();
 36289                var principalSid = Helpers.PreProcessSID(ir);
 290
 291                //Preprocess returns null if this is an ignored sid
 41292                if (principalSid == null) {
 5293                    continue;
 294                }
 295
 31296                var (success, resolvedPrincipal) = await _utils.ResolveIDAndType(principalSid, objectDomain);
 31297                if (!success) {
 0298                    _log.LogTrace("Failed to resolve type for principal {Sid} on ACE for {Object}", principalSid, object
 0299                    resolvedPrincipal.ObjectIdentifier = principalSid;
 0300                    resolvedPrincipal.ObjectType = Label.Base;
 0301                }
 302
 31303                var aceRights = ace.ActiveDirectoryRights();
 304                //Lowercase this just in case. As far as I know it should always come back that way anyways, but better 
 31305                var aceType = ace.ObjectType().ToString().ToLower();
 31306                var inherited = ace.IsInherited();
 307
 31308                var aceInheritanceHash = "";
 36309                if (inherited) {
 5310                    aceInheritanceHash = CalculateInheritanceHash(ir, aceRights, aceType, ace.InheritedObjectType());
 5311                }
 312
 31313                _log.LogTrace("Processing ACE with rights {Rights} and guid {GUID} on object {Name}", aceRights,
 31314                    aceType, objectName);
 315
 316                //GenericAll applies to every object
 36317                if (aceRights.HasFlag(ActiveDirectoryRights.GenericAll)) {
 5318                    if (aceType is ACEGuids.AllGuid or "")
 4319                        yield return new ACE {
 4320                            PrincipalType = resolvedPrincipal.ObjectType,
 4321                            PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 4322                            IsInherited = inherited,
 4323                            RightName = EdgeNames.GenericAll,
 4324                            InheritanceHash = aceInheritanceHash
 4325                        };
 326                    //This is a special case. If we don't continue here, every other ACE will match because GenericAll i
 5327                    continue;
 328                }
 329
 330                //WriteDACL and WriteOwner are always useful no matter what the object type is as well because they enab
 26331                if (aceRights.HasFlag(ActiveDirectoryRights.WriteDacl))
 2332                    yield return new ACE {
 2333                        PrincipalType = resolvedPrincipal.ObjectType,
 2334                        PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 2335                        IsInherited = inherited,
 2336                        RightName = EdgeNames.WriteDacl,
 2337                        InheritanceHash = aceInheritanceHash
 2338                    };
 339
 26340                if (aceRights.HasFlag(ActiveDirectoryRights.WriteOwner))
 2341                    yield return new ACE {
 2342                        PrincipalType = resolvedPrincipal.ObjectType,
 2343                        PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 2344                        IsInherited = inherited,
 2345                        RightName = EdgeNames.WriteOwner,
 2346                        InheritanceHash = aceInheritanceHash
 2347                    };
 348
 349                //Cool ACE courtesy of @rookuu. Allows a principal to add itself to a group and no one else
 26350                if (aceRights.HasFlag(ActiveDirectoryRights.Self) &&
 26351                    !aceRights.HasFlag(ActiveDirectoryRights.WriteProperty) &&
 26352                    !aceRights.HasFlag(ActiveDirectoryRights.GenericWrite) && objectType == Label.Group &&
 26353                    aceType == ACEGuids.WriteMember)
 2354                    yield return new ACE {
 2355                        PrincipalType = resolvedPrincipal.ObjectType,
 2356                        PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 2357                        IsInherited = inherited,
 2358                        RightName = EdgeNames.AddSelf,
 2359                        InheritanceHash = aceInheritanceHash
 2360                    };
 361
 362                //Process object type specific ACEs. Extended rights apply to users, domains, computers, and cert templa
 38363                if (aceRights.HasFlag(ActiveDirectoryRights.ExtendedRight)) {
 16364                    if (objectType == Label.Domain) {
 4365                        if (aceType == ACEGuids.DSReplicationGetChanges)
 1366                            yield return new ACE {
 1367                                PrincipalType = resolvedPrincipal.ObjectType,
 1368                                PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 1369                                IsInherited = inherited,
 1370                                RightName = EdgeNames.GetChanges,
 1371                                InheritanceHash = aceInheritanceHash
 1372                            };
 3373                        else if (aceType == ACEGuids.DSReplicationGetChangesAll)
 1374                            yield return new ACE {
 1375                                PrincipalType = resolvedPrincipal.ObjectType,
 1376                                PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 1377                                IsInherited = inherited,
 1378                                RightName = EdgeNames.GetChangesAll,
 1379                                InheritanceHash = aceInheritanceHash
 1380                            };
 2381                        else if (aceType == ACEGuids.DSReplicationGetChangesInFilteredSet)
 0382                            yield return new ACE {
 0383                                PrincipalType = resolvedPrincipal.ObjectType,
 0384                                PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 0385                                IsInherited = inherited,
 0386                                RightName = EdgeNames.GetChangesInFilteredSet,
 0387                                InheritanceHash = aceInheritanceHash
 0388                            };
 2389                        else if (aceType is ACEGuids.AllGuid or "")
 1390                            yield return new ACE {
 1391                                PrincipalType = resolvedPrincipal.ObjectType,
 1392                                PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 1393                                IsInherited = inherited,
 1394                                RightName = EdgeNames.AllExtendedRights,
 1395                                InheritanceHash = aceInheritanceHash
 1396                            };
 15397                    } else if (objectType == Label.User) {
 3398                        if (aceType == ACEGuids.UserForceChangePassword)
 1399                            yield return new ACE {
 1400                                PrincipalType = resolvedPrincipal.ObjectType,
 1401                                PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 1402                                IsInherited = inherited,
 1403                                RightName = EdgeNames.ForceChangePassword,
 1404                                InheritanceHash = aceInheritanceHash
 1405                            };
 2406                        else if (aceType is ACEGuids.AllGuid or "")
 1407                            yield return new ACE {
 1408                                PrincipalType = resolvedPrincipal.ObjectType,
 1409                                PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 1410                                IsInherited = inherited,
 1411                                RightName = EdgeNames.AllExtendedRights,
 1412                                InheritanceHash = aceInheritanceHash
 1413                            };
 11414                    } else if (objectType == Label.Computer) {
 415                        //ReadLAPSPassword is only applicable if the computer actually has LAPS. Check the world readabl
 5416                        if (hasLaps) {
 2417                            if (aceType is ACEGuids.AllGuid or "")
 1418                                yield return new ACE {
 1419                                    PrincipalType = resolvedPrincipal.ObjectType,
 1420                                    PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 1421                                    IsInherited = inherited,
 1422                                    RightName = EdgeNames.AllExtendedRights,
 1423                                    InheritanceHash = aceInheritanceHash
 1424                                };
 1425                            else if (_guidMap.TryGetValue(aceType, out var lapsAttribute))
 1426                            {
 427                                // Compare the retrieved attribute name against LDAPProperties values
 1428                                if (lapsAttribute == LDAPProperties.LegacyLAPSPassword ||
 1429                                    lapsAttribute == LDAPProperties.LAPSPlaintextPassword ||
 1430                                    lapsAttribute == LDAPProperties.LAPSEncryptedPassword)
 1431                                {
 1432                                    yield return new ACE
 1433                                    {
 1434                                        PrincipalType = resolvedPrincipal.ObjectType,
 1435                                        PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 1436                                        IsInherited = inherited,
 1437                                        RightName = EdgeNames.ReadLAPSPassword,
 1438                                        InheritanceHash = aceInheritanceHash
 1439                                    };
 1440                                }
 1441                            }
 2442                        }
 5443                    } else if (objectType == Label.CertTemplate) {
 0444                        if (aceType is ACEGuids.AllGuid or "")
 0445                            yield return new ACE {
 0446                                PrincipalType = resolvedPrincipal.ObjectType,
 0447                                PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 0448                                IsInherited = inherited,
 0449                                RightName = EdgeNames.AllExtendedRights,
 0450                                InheritanceHash = aceInheritanceHash
 0451                            };
 0452                        else if (aceType is ACEGuids.Enroll)
 0453                            yield return new ACE {
 0454                                PrincipalType = resolvedPrincipal.ObjectType,
 0455                                PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 0456                                IsInherited = inherited,
 0457                                RightName = EdgeNames.Enroll,
 0458                                InheritanceHash = aceInheritanceHash
 0459                            };
 0460                    }
 12461                }
 462
 463                //GenericWrite encapsulates WriteProperty, so process them in tandem to avoid duplicate edges
 26464                if (aceRights.HasFlag(ActiveDirectoryRights.GenericWrite) ||
 32465                    aceRights.HasFlag(ActiveDirectoryRights.WriteProperty)) {
 6466                    if (objectType is Label.User
 6467                        or Label.Group
 6468                        or Label.Computer
 6469                        or Label.GPO
 6470                        or Label.OU
 6471                        or Label.Domain
 6472                        or Label.CertTemplate
 6473                        or Label.RootCA
 6474                        or Label.EnterpriseCA
 6475                        or Label.AIACA
 6476                        or Label.NTAuthStore
 6477                        or Label.IssuancePolicy)
 5478                        if (aceType is ACEGuids.AllGuid or "")
 2479                            yield return new ACE {
 2480                                PrincipalType = resolvedPrincipal.ObjectType,
 2481                                PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 2482                                IsInherited = inherited,
 2483                                RightName = EdgeNames.GenericWrite,
 2484                                InheritanceHash = aceInheritanceHash
 2485                            };
 486
 6487                    if (objectType == Label.User && aceType == ACEGuids.WriteSPN)
 0488                        yield return new ACE {
 0489                            PrincipalType = resolvedPrincipal.ObjectType,
 0490                            PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 0491                            IsInherited = inherited,
 0492                            RightName = EdgeNames.WriteSPN,
 0493                            InheritanceHash = aceInheritanceHash
 0494                        };
 6495                    else if (objectType == Label.Computer && aceType == ACEGuids.WriteAllowedToAct)
 1496                        yield return new ACE {
 1497                            PrincipalType = resolvedPrincipal.ObjectType,
 1498                            PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 1499                            IsInherited = inherited,
 1500                            RightName = EdgeNames.AddAllowedToAct,
 1501                            InheritanceHash = aceInheritanceHash
 1502                        };
 5503                    else if (objectType == Label.Computer && aceType == ACEGuids.UserAccountRestrictions)
 0504                        yield return new ACE {
 0505                            PrincipalType = resolvedPrincipal.ObjectType,
 0506                            PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 0507                            IsInherited = inherited,
 0508                            RightName = EdgeNames.WriteAccountRestrictions,
 0509                            InheritanceHash = aceInheritanceHash
 0510                        };
 5511                    else if (objectType is Label.OU or Label.Domain && aceType == ACEGuids.WriteGPLink)
 0512                        yield return new ACE
 0513                        {
 0514                            PrincipalType = resolvedPrincipal.ObjectType,
 0515                            PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 0516                            IsInherited = inherited,
 0517                            RightName = EdgeNames.WriteGPLink,
 0518                            InheritanceHash = aceInheritanceHash
 0519                        };
 5520                    else if (objectType == Label.Group && aceType == ACEGuids.WriteMember)
 2521                        yield return new ACE {
 2522                            PrincipalType = resolvedPrincipal.ObjectType,
 2523                            PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 2524                            IsInherited = inherited,
 2525                            RightName = EdgeNames.AddMember,
 2526                            InheritanceHash = aceInheritanceHash
 2527                        };
 3528                    else if (objectType is Label.User or Label.Computer && aceType == ACEGuids.AddKeyPrincipal)
 0529                        yield return new ACE {
 0530                            PrincipalType = resolvedPrincipal.ObjectType,
 0531                            PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 0532                            IsInherited = inherited,
 0533                            RightName = EdgeNames.AddKeyCredentialLink,
 0534                            InheritanceHash = aceInheritanceHash
 0535                        };
 3536                    else if (objectType is Label.CertTemplate) {
 0537                        if (aceType == ACEGuids.PKIEnrollmentFlag)
 0538                            yield return new ACE {
 0539                                PrincipalType = resolvedPrincipal.ObjectType,
 0540                                PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 0541                                IsInherited = inherited,
 0542                                RightName = EdgeNames.WritePKIEnrollmentFlag,
 0543                                InheritanceHash = aceInheritanceHash
 0544                            };
 0545                        else if (aceType == ACEGuids.PKINameFlag)
 0546                            yield return new ACE {
 0547                                PrincipalType = resolvedPrincipal.ObjectType,
 0548                                PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 0549                                IsInherited = inherited,
 0550                                RightName = EdgeNames.WritePKINameFlag,
 0551                                InheritanceHash = aceInheritanceHash
 0552                            };
 0553                    }
 6554                }
 555
 556                // EnterpriseCA rights
 26557                if (objectType == Label.EnterpriseCA) {
 0558                    if (aceType is ACEGuids.Enroll)
 0559                        yield return new ACE {
 0560                            PrincipalType = resolvedPrincipal.ObjectType,
 0561                            PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 0562                            IsInherited = inherited,
 0563                            RightName = EdgeNames.Enroll,
 0564                            InheritanceHash = aceInheritanceHash
 0565                        };
 566
 0567                    var cARights = (CertificationAuthorityRights)aceRights;
 568
 569                    // TODO: These if statements are also present in ProcessRegistryEnrollmentPermissions. Move to share
 0570                    if ((cARights & CertificationAuthorityRights.ManageCA) != 0)
 0571                        yield return new ACE {
 0572                            PrincipalType = resolvedPrincipal.ObjectType,
 0573                            PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 0574                            IsInherited = inherited,
 0575                            RightName = EdgeNames.ManageCA,
 0576                            InheritanceHash = aceInheritanceHash
 0577                        };
 0578                    if ((cARights & CertificationAuthorityRights.ManageCertificates) != 0)
 0579                        yield return new ACE {
 0580                            PrincipalType = resolvedPrincipal.ObjectType,
 0581                            PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 0582                            IsInherited = inherited,
 0583                            RightName = EdgeNames.ManageCertificates,
 0584                            InheritanceHash = aceInheritanceHash
 0585                        };
 586
 0587                    if ((cARights & CertificationAuthorityRights.Enroll) != 0)
 0588                        yield return new ACE {
 0589                            PrincipalType = resolvedPrincipal.ObjectType,
 0590                            PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 0591                            IsInherited = inherited,
 0592                            RightName = EdgeNames.Enroll,
 0593                            InheritanceHash = aceInheritanceHash
 0594                        };
 0595                }
 26596            }
 27597        }
 598
 599        /// <summary>
 600        ///     Helper function to use commonlib types and pass to ProcessGMSAReaders
 601        /// </summary>
 602        /// <param name="resolvedSearchResult"></param>
 603        /// <param name="searchResultEntry"></param>
 604        /// <returns></returns>
 605        public IAsyncEnumerable<ACE> ProcessGMSAReaders(ResolvedSearchResult resolvedSearchResult,
 0606            IDirectoryObject searchResultEntry) {
 0607            if (!searchResultEntry.TryGetByteProperty(LDAPProperties.GroupMSAMembership, out var descriptor)) {
 0608                return AsyncEnumerable.Empty<ACE>();
 609            }
 610
 0611            var domain = resolvedSearchResult.Domain;
 0612            var name = resolvedSearchResult.DisplayName;
 613
 0614            return ProcessGMSAReaders(descriptor, name, domain);
 0615        }
 616
 617        /// <summary>
 618        ///     ProcessGMSAMembership with no account name
 619        /// </summary>
 620        /// <param name="groupMSAMembership"></param>
 621        /// <param name="objectDomain"></param>
 622        /// <returns></returns>
 5623        public IAsyncEnumerable<ACE> ProcessGMSAReaders(byte[] groupMSAMembership, string objectDomain) {
 5624            return ProcessGMSAReaders(groupMSAMembership, "", objectDomain);
 5625        }
 626
 627        /// <summary>
 628        ///     Processes the msds-groupmsamembership property and returns ACEs representing principals that can read th
 629        ///     password from an object
 630        /// </summary>
 631        /// <param name="groupMSAMembership"></param>
 632        /// <param name="objectName"></param>
 633        /// <param name="objectDomain"></param>
 634        /// <returns></returns>
 635        public async IAsyncEnumerable<ACE> ProcessGMSAReaders(byte[] groupMSAMembership, string objectName,
 5636            string objectDomain) {
 6637            if (groupMSAMembership == null) {
 1638                _log.LogDebug("GMSA bytes are null for {Name}", objectName);
 1639                yield break;
 640            }
 641
 4642            var descriptor = _utils.MakeSecurityDescriptor();
 4643            try {
 4644                descriptor.SetSecurityDescriptorBinaryForm(groupMSAMembership);
 4645            } catch (OverflowException) {
 0646                _log.LogWarning("GMSA ACL length on object {Name} exceeds allowable length. Unable to process",
 0647                    objectName);
 0648                yield break;
 649            }
 650
 4651            _log.LogDebug("Processing GMSA Readers for {ObjectName}", objectName);
 24652            foreach (var ace in descriptor.GetAccessRules(true, true, typeof(SecurityIdentifier))) {
 6653                if (ace == null || ace.AccessControlType() == AccessControlType.Deny) {
 2654                    continue;
 655                }
 656
 2657                var ir = ace.IdentityReference();
 2658                var principalSid = Helpers.PreProcessSID(ir);
 659
 3660                if (principalSid == null) {
 1661                    continue;
 662                }
 663
 1664                _log.LogTrace("Processing GMSA ACE with principal {Principal}", principalSid);
 665
 2666                if (await _utils.ResolveIDAndType(principalSid, objectDomain) is (true, var resolvedPrincipal)) {
 1667                    yield return new ACE {
 1668                        RightName = EdgeNames.ReadGMSAPassword,
 1669                        PrincipalType = resolvedPrincipal.ObjectType,
 1670                        PrincipalSID = resolvedPrincipal.ObjectIdentifier,
 1671                        IsInherited = ace.IsInherited()
 1672                    };
 1673                }
 1674            }
 5675        }
 676    }
 677}