< Summary

Class:SharpHoundCommonLib.Processors.LDAPPropertyProcessor
Assembly:SharpHoundCommonLib
File(s):D:\a\SharpHoundCommon\SharpHoundCommon\src\CommonLib\Processors\LDAPPropertyProcessor.cs
Covered lines:363
Uncovered lines:90
Coverable lines:453
Total lines:819
Line coverage:80.1% (363 of 453)
Covered branches:99
Total branches:161
Branch coverage:61.4% (99 of 161)

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%10100%
.ctor(...)100%10100%
GetCommonProps(...)100%10100%
ReadDomainProperties(...)100%20100%
FunctionalLevelToString(...)100%90100%
ReadGPOProperties(...)50%20100%
ReadOUProperties(...)100%10100%
ReadGroupProperties(...)100%20100%
ReadContainerProperties(...)100%100%
ReadUserProperties()90%20098.79%
ReadComputerProperties()78.57%28089.88%
ReadRootCAProperties(...)50%2042.85%
ReadAIACAProperties(...)50%2055.55%
ReadEnterpriseCAProperties(...)0%400%
ReadNTAuthStoreProperties(...)100%10100%
ReadCertTemplateProperties(...)91.66%120100%
ReadIssuancePolicyProperties(...)100%40100%
ParseAllProperties(...)80%20074.19%
ParseCertTemplateApplicationPolicies(...)50%12056.25%
BestGuessConvert(...)50%80100%
ConvertPKIPeriod(...)5.88%34012.5%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Diagnostics.CodeAnalysis;
 4using System.Linq;
 5using System.Reflection;
 6using System.Runtime.InteropServices;
 7using System.Security.AccessControl;
 8using System.Security.Cryptography.X509Certificates;
 9using System.Security.Principal;
 10using System.Threading.Tasks;
 11using SharpHoundCommonLib.Enums;
 12using SharpHoundCommonLib.LDAPQueries;
 13using SharpHoundCommonLib.OutputTypes;
 14
 15namespace SharpHoundCommonLib.Processors
 16{
 17    public class LDAPPropertyProcessor
 18    {
 119        private static readonly string[] ReservedAttributes = CommonProperties.TypeResolutionProps
 120            .Concat(CommonProperties.BaseQueryProps).Concat(CommonProperties.GroupResolutionProps)
 121            .Concat(CommonProperties.ComputerMethodProps).Concat(CommonProperties.ACLProps)
 122            .Concat(CommonProperties.ObjectPropsProps).Concat(CommonProperties.ContainerProps)
 123            .Concat(CommonProperties.SPNTargetProps).Concat(CommonProperties.DomainTrustProps)
 124            .Concat(CommonProperties.GPOLocalGroupProps).ToArray();
 25
 26        private readonly ILDAPUtils _utils;
 27
 1428        public LDAPPropertyProcessor(ILDAPUtils utils)
 1429        {
 1430            _utils = utils;
 1431        }
 32
 33        private static Dictionary<string, object> GetCommonProps(ISearchResultEntry entry)
 2034        {
 2035            return new Dictionary<string, object>
 2036            {
 2037                {
 2038                    "description", entry.GetProperty(LDAPProperties.Description)
 2039                },
 2040                {
 2041                    "whencreated", Helpers.ConvertTimestampToUnixEpoch(entry.GetProperty(LDAPProperties.WhenCreated))
 2042                }
 2043            };
 2044        }
 45
 46        /// <summary>
 47        ///     Reads specific LDAP properties related to Domains
 48        /// </summary>
 49        /// <param name="entry"></param>
 50        /// <returns></returns>
 51        public static Dictionary<string, object> ReadDomainProperties(ISearchResultEntry entry)
 252        {
 253            var props = GetCommonProps(entry);
 54
 355            if (!int.TryParse(entry.GetProperty(LDAPProperties.DomainFunctionalLevel), out var level)) level = -1;
 56
 257            props.Add("functionallevel", FunctionalLevelToString(level));
 58
 259            return props;
 260        }
 61
 62        /// <summary>
 63        ///     Converts a numeric representation of a functional level to its appropriate functional level string
 64        /// </summary>
 65        /// <param name="level"></param>
 66        /// <returns></returns>
 67        public static string FunctionalLevelToString(int level)
 1168        {
 1169            var functionalLevel = level switch
 1170            {
 171                0 => "2000 Mixed/Native",
 172                1 => "2003 Interim",
 173                2 => "2003",
 174                3 => "2008",
 175                4 => "2008 R2",
 176                5 => "2012",
 277                6 => "2012 R2",
 178                7 => "2016",
 279                _ => "Unknown"
 1180            };
 81
 1182            return functionalLevel;
 1183        }
 84
 85        /// <summary>
 86        ///     Reads specific LDAP properties related to GPOs
 87        /// </summary>
 88        /// <param name="entry"></param>
 89        /// <returns></returns>
 90        public static Dictionary<string, object> ReadGPOProperties(ISearchResultEntry entry)
 191        {
 192            var props = GetCommonProps(entry);
 193            props.Add("gpcpath", entry.GetProperty(LDAPProperties.GPCFileSYSPath)?.ToUpper());
 194            return props;
 195        }
 96
 97        /// <summary>
 98        ///     Reads specific LDAP properties related to OUs
 99        /// </summary>
 100        /// <param name="entry"></param>
 101        /// <returns></returns>
 102        public static Dictionary<string, object> ReadOUProperties(ISearchResultEntry entry)
 1103        {
 1104            var props = GetCommonProps(entry);
 1105            return props;
 1106        }
 107
 108        /// <summary>
 109        ///     Reads specific LDAP properties related to Groups
 110        /// </summary>
 111        /// <param name="entry"></param>
 112        /// <returns></returns>
 113        public static Dictionary<string, object> ReadGroupProperties(ISearchResultEntry entry)
 3114        {
 3115            var props = GetCommonProps(entry);
 116
 3117            var ac = entry.GetProperty(LDAPProperties.AdminCount);
 3118            if (ac != null)
 2119            {
 2120                var a = int.Parse(ac);
 2121                props.Add("admincount", a != 0);
 2122            }
 123            else
 1124            {
 1125                props.Add("admincount", false);
 1126            }
 127
 3128            return props;
 3129        }
 130
 131        /// <summary>
 132        ///     Reads specific LDAP properties related to containers
 133        /// </summary>
 134        /// <param name="entry"></param>
 135        /// <returns></returns>
 136        public static Dictionary<string, object> ReadContainerProperties(ISearchResultEntry entry)
 0137        {
 0138            var props = GetCommonProps(entry);
 0139            return props;
 0140        }
 141
 142        /// <summary>
 143        ///     Reads specific LDAP properties related to Users
 144        /// </summary>
 145        /// <param name="entry"></param>
 146        /// <returns></returns>
 147        public async Task<UserProperties> ReadUserProperties(ISearchResultEntry entry)
 4148        {
 4149            var userProps = new UserProperties();
 4150            var props = GetCommonProps(entry);
 151
 4152            var uacFlags = (UacFlags)0;
 4153            var uac = entry.GetProperty(LDAPProperties.UserAccountControl);
 4154            if (int.TryParse(uac, out var flag))
 3155            {
 3156                uacFlags = (UacFlags)flag;
 3157            }
 158
 4159            props.Add("sensitive", uacFlags.HasFlag(UacFlags.NotDelegated));
 4160            props.Add("dontreqpreauth", uacFlags.HasFlag(UacFlags.DontReqPreauth));
 4161            props.Add("passwordnotreqd", uacFlags.HasFlag(UacFlags.PasswordNotRequired));
 4162            props.Add("unconstraineddelegation", uacFlags.HasFlag(UacFlags.TrustedForDelegation));
 4163            props.Add("pwdneverexpires", uacFlags.HasFlag(UacFlags.DontExpirePassword));
 4164            props.Add("enabled", !uacFlags.HasFlag(UacFlags.AccountDisable));
 4165            props.Add("trustedtoauth", uacFlags.HasFlag(UacFlags.TrustedToAuthForDelegation));
 166
 4167            var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName);
 168
 4169            var comps = new List<TypedPrincipal>();
 4170            if (uacFlags.HasFlag(UacFlags.TrustedToAuthForDelegation))
 1171            {
 1172                var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo);
 1173                props.Add("allowedtodelegate", delegates);
 174
 7175                foreach (var d in delegates)
 2176                {
 2177                    if (d == null)
 0178                        continue;
 179
 2180                    var resolvedHost = await _utils.ResolveHostToSid(d, domain);
 2181                    if (resolvedHost != null && resolvedHost.Contains("S-1"))
 2182                        comps.Add(new TypedPrincipal
 2183                        {
 2184                            ObjectIdentifier = resolvedHost,
 2185                            ObjectType = Label.Computer
 2186                        });
 2187                }
 1188            }
 189
 4190            userProps.AllowedToDelegate = comps.Distinct().ToArray();
 191
 4192            props.Add("lastlogon", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogon)));
 4193            props.Add("lastlogontimestamp",
 4194                Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogonTimestamp)));
 4195            props.Add("pwdlastset",
 4196                Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.PasswordLastSet)));
 4197            var spn = entry.GetArrayProperty(LDAPProperties.ServicePrincipalNames);
 4198            props.Add("serviceprincipalnames", spn);
 4199            props.Add("hasspn", spn.Length > 0);
 4200            props.Add("displayname", entry.GetProperty(LDAPProperties.DisplayName));
 4201            props.Add("email", entry.GetProperty(LDAPProperties.Email));
 4202            props.Add("title", entry.GetProperty(LDAPProperties.Title));
 4203            props.Add("homedirectory", entry.GetProperty(LDAPProperties.HomeDirectory));
 4204            props.Add("userpassword", entry.GetProperty(LDAPProperties.UserPassword));
 4205            props.Add("unixpassword", entry.GetProperty(LDAPProperties.UnixUserPassword));
 4206            props.Add("unicodepassword", entry.GetProperty(LDAPProperties.UnicodePassword));
 4207            props.Add("sfupassword", entry.GetProperty(LDAPProperties.MsSFU30Password));
 4208            props.Add("logonscript", entry.GetProperty(LDAPProperties.ScriptPath));
 209
 4210            var ac = entry.GetProperty(LDAPProperties.AdminCount);
 4211            if (ac != null)
 3212            {
 3213                if (int.TryParse(ac, out var parsed))
 2214                    props.Add("admincount", parsed != 0);
 215                else
 1216                    props.Add("admincount", false);
 3217            }
 218            else
 1219            {
 1220                props.Add("admincount", false);
 1221            }
 222
 4223            var sh = entry.GetByteArrayProperty(LDAPProperties.SIDHistory);
 4224            var sidHistoryList = new List<string>();
 4225            var sidHistoryPrincipals = new List<TypedPrincipal>();
 20226            foreach (var sid in sh)
 4227            {
 228                string sSid;
 229                try
 4230                {
 4231                    sSid = new SecurityIdentifier(sid, 0).Value;
 3232                }
 1233                catch
 1234                {
 1235                    continue;
 236                }
 237
 3238                sidHistoryList.Add(sSid);
 239
 3240                var res = _utils.ResolveIDAndType(sSid, domain);
 241
 3242                sidHistoryPrincipals.Add(res);
 3243            }
 244
 4245            userProps.SidHistory = sidHistoryPrincipals.Distinct().ToArray();
 246
 4247            props.Add("sidhistory", sidHistoryList.ToArray());
 248
 4249            userProps.Props = props;
 250
 4251            return userProps;
 4252        }
 253
 254        /// <summary>
 255        ///     Reads specific LDAP properties related to Computers
 256        /// </summary>
 257        /// <param name="entry"></param>
 258        /// <returns></returns>
 259        public async Task<ComputerProperties> ReadComputerProperties(ISearchResultEntry entry)
 3260        {
 3261            var compProps = new ComputerProperties();
 3262            var props = GetCommonProps(entry);
 263
 3264            var flags = (UacFlags)0;
 3265            var uac = entry.GetProperty(LDAPProperties.UserAccountControl);
 3266            if (int.TryParse(uac, out var flag))
 2267            {
 2268                flags = (UacFlags)flag;
 2269            }
 270
 3271            props.Add("enabled", !flags.HasFlag(UacFlags.AccountDisable));
 3272            props.Add("unconstraineddelegation", flags.HasFlag(UacFlags.TrustedForDelegation));
 3273            props.Add("trustedtoauth", flags.HasFlag(UacFlags.TrustedToAuthForDelegation));
 3274            props.Add("isdc", flags.HasFlag(UacFlags.ServerTrustAccount));
 275
 3276            var domain = Helpers.DistinguishedNameToDomain(entry.DistinguishedName);
 277
 3278            var comps = new List<TypedPrincipal>();
 3279            if (flags.HasFlag(UacFlags.TrustedToAuthForDelegation))
 2280            {
 2281                var delegates = entry.GetArrayProperty(LDAPProperties.AllowedToDelegateTo);
 2282                props.Add("allowedtodelegate", delegates);
 283
 18284                foreach (var d in delegates)
 6285                {
 6286                    var hname = d.Contains("/") ? d.Split('/')[1] : d;
 6287                    hname = hname.Split(':')[0];
 6288                    var resolvedHost = await _utils.ResolveHostToSid(hname, domain);
 6289                    if (resolvedHost != null && resolvedHost.Contains("S-1"))
 6290                        comps.Add(new TypedPrincipal
 6291                        {
 6292                            ObjectIdentifier = resolvedHost,
 6293                            ObjectType = Label.Computer
 6294                        });
 6295                }
 2296            }
 297
 3298            compProps.AllowedToDelegate = comps.Distinct().ToArray();
 299
 3300            var allowedToActPrincipals = new List<TypedPrincipal>();
 3301            var rawAllowedToAct = entry.GetByteProperty(LDAPProperties.AllowedToActOnBehalfOfOtherIdentity);
 3302            if (rawAllowedToAct != null)
 0303            {
 0304                var sd = _utils.MakeSecurityDescriptor();
 0305                sd.SetSecurityDescriptorBinaryForm(rawAllowedToAct, AccessControlSections.Access);
 0306                foreach (var rule in sd.GetAccessRules(true, true, typeof(SecurityIdentifier)))
 0307                {
 0308                    var res = _utils.ResolveIDAndType(rule.IdentityReference(), domain);
 0309                    allowedToActPrincipals.Add(res);
 0310                }
 0311            }
 312
 3313            compProps.AllowedToAct = allowedToActPrincipals.ToArray();
 314
 3315            props.Add("lastlogon", Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogon)));
 3316            props.Add("lastlogontimestamp",
 3317                Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.LastLogonTimestamp)));
 3318            props.Add("pwdlastset",
 3319                Helpers.ConvertFileTimeToUnixEpoch(entry.GetProperty(LDAPProperties.PasswordLastSet)));
 3320            props.Add("serviceprincipalnames", entry.GetArrayProperty(LDAPProperties.ServicePrincipalNames));
 3321            props.Add("email", entry.GetProperty(LDAPProperties.Email));
 3322            var os = entry.GetProperty(LDAPProperties.OperatingSystem);
 3323            var sp = entry.GetProperty(LDAPProperties.ServicePack);
 324
 5325            if (sp != null) os = $"{os} {sp}";
 326
 3327            props.Add("operatingsystem", os);
 328
 3329            var sh = entry.GetByteArrayProperty(LDAPProperties.SIDHistory);
 3330            var sidHistoryList = new List<string>();
 3331            var sidHistoryPrincipals = new List<TypedPrincipal>();
 15332            foreach (var sid in sh)
 3333            {
 334                string sSid;
 335                try
 3336                {
 3337                    sSid = new SecurityIdentifier(sid, 0).Value;
 2338                }
 1339                catch
 1340                {
 1341                    continue;
 342                }
 343
 2344                sidHistoryList.Add(sSid);
 345
 2346                var res = _utils.ResolveIDAndType(sSid, domain);
 347
 2348                sidHistoryPrincipals.Add(res);
 2349            }
 350
 3351            compProps.SidHistory = sidHistoryPrincipals.ToArray();
 352
 3353            props.Add("sidhistory", sidHistoryList.ToArray());
 354
 3355            var hsa = entry.GetArrayProperty(LDAPProperties.HostServiceAccount);
 3356            var smsaPrincipals = new List<TypedPrincipal>();
 3357            if (hsa != null)
 3358            {
 13359                foreach (var dn in hsa)
 2360                {
 2361                    var resolvedPrincipal = _utils.ResolveDistinguishedName(dn);
 362
 2363                    if (resolvedPrincipal != null)
 2364                        smsaPrincipals.Add(resolvedPrincipal);
 2365                }
 3366            }
 367
 3368            compProps.DumpSMSAPassword = smsaPrincipals.ToArray();
 369
 3370            compProps.Props = props;
 371
 3372            return compProps;
 3373        }
 374
 375        /// <summary>
 376        /// Returns the properties associated with the RootCA
 377        /// </summary>
 378        /// <param name="entry"></param>
 379        /// <returns>Returns a dictionary with the common properties of the RootCA</returns>
 380        public static Dictionary<string, object> ReadRootCAProperties(ISearchResultEntry entry)
 1381        {
 1382            var props = GetCommonProps(entry);
 383
 384            // Certificate
 1385            var rawCertificate = entry.GetByteProperty(LDAPProperties.CACertificate);
 1386            if (rawCertificate != null)
 0387            {
 0388                var cert = new ParsedCertificate(rawCertificate);
 0389                props.Add("certthumbprint", cert.Thumbprint);
 0390                props.Add("certname", cert.Name);
 0391                props.Add("certchain", cert.Chain);
 0392                props.Add("hasbasicconstraints", cert.HasBasicConstraints);
 0393                props.Add("basicconstraintpathlength", cert.BasicConstraintPathLength);
 0394            }
 395
 1396            return props;
 1397        }
 398
 399        /// <summary>
 400        /// Returns the properties associated with the AIACA
 401        /// </summary>
 402        /// <param name="entry"></param>
 403        /// <returns>Returns a dictionary with the common properties and the crosscertificatepair property of the AICA</
 404        public static Dictionary<string, object> ReadAIACAProperties(ISearchResultEntry entry)
 1405        {
 1406            var props = GetCommonProps(entry);
 1407            var crossCertificatePair = entry.GetByteArrayProperty((LDAPProperties.CrossCertificatePair));
 1408            var hasCrossCertificatePair = crossCertificatePair.Length > 0;
 409
 1410            props.Add("crosscertificatepair", crossCertificatePair);
 1411            props.Add("hascrosscertificatepair", hasCrossCertificatePair);
 412
 413            // Certificate
 1414            var rawCertificate = entry.GetByteProperty(LDAPProperties.CACertificate);
 1415            if (rawCertificate != null)
 0416            {
 0417                var cert = new ParsedCertificate(rawCertificate);
 0418                props.Add("certthumbprint", cert.Thumbprint);
 0419                props.Add("certname", cert.Name);
 0420                props.Add("certchain", cert.Chain);
 0421                props.Add("hasbasicconstraints", cert.HasBasicConstraints);
 0422                props.Add("basicconstraintpathlength", cert.BasicConstraintPathLength);
 0423            }
 424
 1425            return props;
 1426        }
 427
 428        public static Dictionary<string, object> ReadEnterpriseCAProperties(ISearchResultEntry entry)
 0429        {
 0430            var props = GetCommonProps(entry);
 0431            if (entry.GetIntProperty("flags", out var flags)) props.Add("flags", (PKICertificateAuthorityFlags)flags);
 0432            props.Add("caname", entry.GetProperty(LDAPProperties.Name));
 0433            props.Add("dnshostname", entry.GetProperty(LDAPProperties.DNSHostName));
 434
 435            // Certificate
 0436            var rawCertificate = entry.GetByteProperty(LDAPProperties.CACertificate);
 0437            if (rawCertificate != null)
 0438            {
 0439                var cert = new ParsedCertificate(rawCertificate);
 0440                props.Add("certthumbprint", cert.Thumbprint);
 0441                props.Add("certname", cert.Name);
 0442                props.Add("certchain", cert.Chain);
 0443                props.Add("hasbasicconstraints", cert.HasBasicConstraints);
 0444                props.Add("basicconstraintpathlength", cert.BasicConstraintPathLength);
 0445            }
 446
 0447            return props;
 0448        }
 449
 450        /// <summary>
 451        /// Returns the properties associated with the NTAuthStore. These properties will only contain common properties
 452        /// </summary>
 453        /// <param name="entry"></param>
 454        /// <returns>Returns a dictionary with the common properties of the NTAuthStore</returns>
 455        public static Dictionary<string, object> ReadNTAuthStoreProperties(ISearchResultEntry entry)
 1456        {
 1457            var props = GetCommonProps(entry);
 1458            return props;
 1459        }
 460
 461        /// <summary>
 462        /// Reads specific LDAP properties related to CertTemplates
 463        /// </summary>
 464        /// <param name="entry"></param>
 465        /// <returns>Returns a dictionary associated with the CertTemplate properties that were read</returns>
 466        public static Dictionary<string, object> ReadCertTemplateProperties(ISearchResultEntry entry)
 1467        {
 1468            var props = GetCommonProps(entry);
 469
 1470            props.Add("validityperiod", ConvertPKIPeriod(entry.GetByteProperty(LDAPProperties.PKIExpirationPeriod)));
 1471            props.Add("renewalperiod", ConvertPKIPeriod(entry.GetByteProperty(LDAPProperties.PKIOverlappedPeriod)));
 472
 1473            if (entry.GetIntProperty(LDAPProperties.TemplateSchemaVersion, out var schemaVersion))
 1474                props.Add("schemaversion", schemaVersion);
 475
 1476            props.Add("displayname", entry.GetProperty(LDAPProperties.DisplayName));
 1477            props.Add("oid", entry.GetProperty(LDAPProperties.CertTemplateOID));
 478
 1479            if (entry.GetIntProperty(LDAPProperties.PKIEnrollmentFlag, out var enrollmentFlagsRaw))
 1480            {
 1481                var enrollmentFlags = (PKIEnrollmentFlag)enrollmentFlagsRaw;
 482
 1483                props.Add("enrollmentflag", enrollmentFlags);
 1484                props.Add("requiresmanagerapproval", enrollmentFlags.HasFlag(PKIEnrollmentFlag.PEND_ALL_REQUESTS));
 1485                props.Add("nosecurityextension", enrollmentFlags.HasFlag(PKIEnrollmentFlag.NO_SECURITY_EXTENSION));
 1486            }
 487
 1488            if (entry.GetIntProperty(LDAPProperties.PKINameFlag, out var nameFlagsRaw))
 1489            {
 1490                var nameFlags = (PKICertificateNameFlag)nameFlagsRaw;
 491
 1492                props.Add("certificatenameflag", nameFlags);
 1493                props.Add("enrolleesuppliessubject",
 1494                    nameFlags.HasFlag(PKICertificateNameFlag.ENROLLEE_SUPPLIES_SUBJECT));
 1495                props.Add("subjectaltrequireupn",
 1496                    nameFlags.HasFlag(PKICertificateNameFlag.SUBJECT_ALT_REQUIRE_UPN));
 1497                props.Add("subjectaltrequiredns",
 1498                    nameFlags.HasFlag(PKICertificateNameFlag.SUBJECT_ALT_REQUIRE_DNS));
 1499                props.Add("subjectaltrequiredomaindns",
 1500                    nameFlags.HasFlag(PKICertificateNameFlag.SUBJECT_ALT_REQUIRE_DOMAIN_DNS));
 1501                props.Add("subjectaltrequireemail",
 1502                    nameFlags.HasFlag(PKICertificateNameFlag.SUBJECT_ALT_REQUIRE_EMAIL));
 1503                props.Add("subjectaltrequirespn",
 1504                    nameFlags.HasFlag(PKICertificateNameFlag.SUBJECT_ALT_REQUIRE_SPN));
 1505                props.Add("subjectrequireemail",
 1506                    nameFlags.HasFlag(PKICertificateNameFlag.SUBJECT_REQUIRE_EMAIL));
 1507            }
 508
 1509            var ekus = entry.GetArrayProperty(LDAPProperties.ExtendedKeyUsage);
 1510            props.Add("ekus", ekus);
 1511            var certificateApplicationPolicy = entry.GetArrayProperty(LDAPProperties.CertificateApplicationPolicy);
 1512            props.Add("certificateapplicationpolicy", certificateApplicationPolicy);
 513
 1514            var certificatePolicy = entry.GetArrayProperty(LDAPProperties.CertificatePolicy);
 1515            props.Add("certificatepolicy", certificatePolicy);
 516
 1517            if (entry.GetIntProperty(LDAPProperties.NumSignaturesRequired, out var authorizedSignatures))
 1518                props.Add("authorizedsignatures", authorizedSignatures);
 519
 1520            var hasUseLegacyProvider = false;
 1521            if (entry.GetIntProperty(LDAPProperties.PKIPrivateKeyFlag, out var privateKeyFlagsRaw))
 1522            {
 1523                var privateKeyFlags = (PKIPrivateKeyFlag)privateKeyFlagsRaw;
 1524                hasUseLegacyProvider = privateKeyFlags.HasFlag(PKIPrivateKeyFlag.USE_LEGACY_PROVIDER);
 1525            }
 526
 1527            props.Add("applicationpolicies", ParseCertTemplateApplicationPolicies(entry.GetArrayProperty(LDAPProperties.
 1528            props.Add("issuancepolicies", entry.GetArrayProperty(LDAPProperties.IssuancePolicies));
 529
 530            // Construct effectiveekus
 1531            var effectiveekus = schemaVersion == 1 & ekus.Length > 0 ? ekus : certificateApplicationPolicy;
 1532            props.Add("effectiveekus", effectiveekus);
 533
 534            // Construct authenticationenabled
 1535            var authenticationEnabled = effectiveekus.Intersect(Helpers.AuthenticationOIDs).Any() | effectiveekus.Length
 1536            props.Add("authenticationenabled", authenticationEnabled);
 537
 1538            return props;
 1539        }
 540
 541        public IssuancePolicyProperties ReadIssuancePolicyProperties(ISearchResultEntry entry)
 2542        {
 2543            var ret = new IssuancePolicyProperties();
 2544            var props = GetCommonProps(entry);
 2545            props.Add("displayname", entry.GetProperty(LDAPProperties.DisplayName));
 2546            props.Add("certtemplateoid", entry.GetProperty(LDAPProperties.CertTemplateOID));
 547
 2548            var link = entry.GetProperty(LDAPProperties.OIDGroupLink);
 2549            if (!string.IsNullOrEmpty(link))
 1550            {
 1551                var linkedGroup = _utils.ResolveDistinguishedName(link);
 1552                if (linkedGroup != null)
 1553                {
 1554                    props.Add("oidgrouplink", linkedGroup.ObjectIdentifier);
 1555                    ret.GroupLink = linkedGroup;
 1556                }
 1557            }
 558
 2559            ret.Props = props;
 2560            return ret;
 2561        }
 562
 563        /// <summary>
 564        ///     Attempts to parse all LDAP attributes outside of the ones already collected and converts them to a human
 565        ///     format using a best guess
 566        /// </summary>
 567        /// <param name="entry"></param>
 568        public Dictionary<string, object> ParseAllProperties(ISearchResultEntry entry)
 5569        {
 5570            var props = new Dictionary<string, object>();
 571
 5572            var type = typeof(LDAPProperties);
 340573            var reserved = type.GetFields(BindingFlags.Static | BindingFlags.Public).Select(x => x.GetValue(null).ToStri
 574
 31575            foreach (var property in entry.PropertyNames())
 8576            {
 8577                if (ReservedAttributes.Contains(property, StringComparer.OrdinalIgnoreCase))
 3578                    continue;
 579
 5580                var collCount = entry.PropCount(property);
 5581                if (collCount == 0)
 0582                    continue;
 583
 5584                if (collCount == 1)
 5585                {
 5586                    var testBytes = entry.GetByteProperty(property);
 587
 6588                    if (testBytes == null || testBytes.Length == 0) continue;
 589
 4590                    var testString = entry.GetProperty(property);
 591
 4592                    if (!string.IsNullOrEmpty(testString))
 4593                        if (property == "badpasswordtime")
 1594                            props.Add(property, Helpers.ConvertFileTimeToUnixEpoch(testString));
 595                        else
 3596                            props.Add(property, BestGuessConvert(testString));
 4597                }
 598                else
 0599                {
 0600                    var arrBytes = entry.GetByteArrayProperty(property);
 0601                    if (arrBytes.Length == 0)
 0602                        continue;
 603
 0604                    var arr = entry.GetArrayProperty(property);
 0605                    if (arr.Length > 0) props.Add(property, arr.Select(BestGuessConvert).ToArray());
 0606                }
 4607            }
 608
 5609            return props;
 5610        }
 611
 612        /// <summary>
 613        ///     Parse CertTemplate attribute msPKI-RA-Application-Policies
 614        /// </summary>
 615        /// <param name="applicationPolicies"></param>
 616        /// <param name="schemaVersion"></param>
 617        /// <param name="hasUseLegacyProvider"></param>
 618        private static string[] ParseCertTemplateApplicationPolicies(string[] applicationPolicies, int schemaVersion, bo
 1619        {
 1620            if (applicationPolicies == null
 1621                || applicationPolicies.Length == 0
 1622                || schemaVersion == 1
 1623                || schemaVersion == 2
 1624                || (schemaVersion == 4 && hasUseLegacyProvider))
 1625            {
 1626                return applicationPolicies;
 627            }
 628            else
 0629            {
 630                // Format: "Name`Type`Value`Name`Type`Value`..."
 631                // (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-crtd/c55ec697-be3f-4117-8316-8895e4
 632                // Return the Value of Name = "msPKI-RA-Application-Policies" entries
 0633                var entries = applicationPolicies[0].Split('`');
 0634                return Enumerable.Range(0, entries.Length / 3)
 0635                    .Select(i => entries.Skip(i * 3).Take(3).ToArray())
 0636                    .Where(parts => parts.Length == 3 && parts[0].Equals(LDAPProperties.ApplicationPolicies, StringCompa
 0637                    .Select(parts => parts[2])
 0638                    .ToArray();
 639            }
 1640        }
 641
 642        /// <summary>
 643        ///     Does a best guess conversion of the property to a type useable by the UI
 644        /// </summary>
 645        /// <param name="property"></param>
 646        /// <returns></returns>
 647        private static object BestGuessConvert(string property)
 3648        {
 649            //Parse boolean values
 3650            if (bool.TryParse(property, out var boolResult)) return boolResult;
 651
 652            //A string ending with 0Z is likely a timestamp
 3653            if (property.EndsWith("0Z")) return Helpers.ConvertTimestampToUnixEpoch(property);
 654
 655            //This string corresponds to the max int, and is usually set in accountexpires
 3656            if (property == "9223372036854775807") return -1;
 657
 658            //Try parsing as an int
 3659            if (int.TryParse(property, out var num)) return num;
 660
 661            //Just return the property as a string
 3662            return property;
 3663        }
 664
 665        /// <summary>
 666        ///     Converts PKIExpirationPeriod/PKIOverlappedPeriod attributes to time approximate times
 667        /// </summary>
 668        /// <remarks>https://www.sysadmins.lv/blog-en/how-to-convert-pkiexirationperiod-and-pkioverlapperiod-active-dire
 669        /// <param name="bytes"></param>
 670        /// <returns>Returns a string representing the time period associated with the input byte array in a human reada
 671        private static string ConvertPKIPeriod(byte[] bytes)
 2672        {
 2673            if (bytes == null || bytes.Length == 0)
 2674                return "Unknown";
 675
 676            try
 0677            {
 0678                Array.Reverse(bytes);
 0679                var temp = BitConverter.ToString(bytes).Replace("-", "");
 0680                var value = Convert.ToInt64(temp, 16) * -.0000001;
 681
 0682                if (value % 31536000 == 0 && value / 31536000 >= 1)
 0683                {
 0684                    if (value / 31536000 == 1) return "1 year";
 685
 0686                    return $"{value / 31536000} years";
 687                }
 688
 0689                if (value % 2592000 == 0 && value / 2592000 >= 1)
 0690                {
 0691                    if (value / 2592000 == 1) return "1 month";
 692
 0693                    return $"{value / 2592000} months";
 694                }
 695
 0696                if (value % 604800 == 0 && value / 604800 >= 1)
 0697                {
 0698                    if (value / 604800 == 1) return "1 week";
 699
 0700                    return $"{value / 604800} weeks";
 701                }
 702
 0703                if (value % 86400 == 0 && value / 86400 >= 1)
 0704                {
 0705                    if (value / 86400 == 1) return "1 day";
 706
 0707                    return $"{value / 86400} days";
 708                }
 709
 0710                if (value % 3600 == 0 && value / 3600 >= 1)
 0711                {
 0712                    if (value / 3600 == 1) return "1 hour";
 713
 0714                    return $"{value / 3600} hours";
 715                }
 716
 0717                return "";
 718            }
 0719            catch (Exception)
 0720            {
 0721                return "Unknown";
 722            }
 2723        }
 724
 725        [DllImport("Advapi32", SetLastError = false)]
 726        private static extern bool IsTextUnicode(byte[] buf, int len, ref IsTextUnicodeFlags opt);
 727
 728        [Flags]
 729        [SuppressMessage("ReSharper", "UnusedMember.Local")]
 730        [SuppressMessage("ReSharper", "InconsistentNaming")]
 731        private enum IsTextUnicodeFlags
 732        {
 733            IS_TEXT_UNICODE_ASCII16 = 0x0001,
 734            IS_TEXT_UNICODE_REVERSE_ASCII16 = 0x0010,
 735
 736            IS_TEXT_UNICODE_STATISTICS = 0x0002,
 737            IS_TEXT_UNICODE_REVERSE_STATISTICS = 0x0020,
 738
 739            IS_TEXT_UNICODE_CONTROLS = 0x0004,
 740            IS_TEXT_UNICODE_REVERSE_CONTROLS = 0x0040,
 741
 742            IS_TEXT_UNICODE_SIGNATURE = 0x0008,
 743            IS_TEXT_UNICODE_REVERSE_SIGNATURE = 0x0080,
 744
 745            IS_TEXT_UNICODE_ILLEGAL_CHARS = 0x0100,
 746            IS_TEXT_UNICODE_ODD_LENGTH = 0x0200,
 747            IS_TEXT_UNICODE_DBCS_LEADBYTE = 0x0400,
 748            IS_TEXT_UNICODE_NULL_BYTES = 0x1000,
 749
 750            IS_TEXT_UNICODE_UNICODE_MASK = 0x000F,
 751            IS_TEXT_UNICODE_REVERSE_MASK = 0x00F0,
 752            IS_TEXT_UNICODE_NOT_UNICODE_MASK = 0x0F00,
 753            IS_TEXT_UNICODE_NOT_ASCII_MASK = 0xF000
 754        }
 755    }
 756
 757    public class ParsedCertificate
 758    {
 759        public string Thumbprint { get; set; }
 760        public string Name { get; set; }
 761        public string[] Chain { get; set; } = Array.Empty<string>();
 762        public bool HasBasicConstraints { get; set; } = false;
 763        public int BasicConstraintPathLength { get; set; }
 764
 765        public ParsedCertificate(byte[] rawCertificate)
 766        {
 767            var parsedCertificate = new X509Certificate2(rawCertificate);
 768            Thumbprint = parsedCertificate.Thumbprint;
 769            var name = parsedCertificate.FriendlyName;
 770            Name = string.IsNullOrEmpty(name) ? Thumbprint : name;
 771
 772            // Chain
 773            X509Chain chain = new X509Chain();
 774            chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
 775            chain.Build(parsedCertificate);
 776            var temp = new List<string>();
 777            foreach (X509ChainElement cert in chain.ChainElements) temp.Add(cert.Certificate.Thumbprint);
 778            Chain = temp.ToArray();
 779
 780            // Extensions
 781            X509ExtensionCollection extensions = parsedCertificate.Extensions;
 782            List<CertificateExtension> certificateExtensions = new List<CertificateExtension>();
 783            foreach (X509Extension extension in extensions)
 784            {
 785                CertificateExtension certificateExtension = new CertificateExtension(extension);
 786                switch (certificateExtension.Oid.Value)
 787                {
 788                    case CAExtensionTypes.BasicConstraints:
 789                        X509BasicConstraintsExtension ext = (X509BasicConstraintsExtension)extension;
 790                        HasBasicConstraints = ext.HasPathLengthConstraint;
 791                        BasicConstraintPathLength = ext.PathLengthConstraint;
 792                        break;
 793                }
 794            }
 795        }
 796    }
 797
 798    public class UserProperties
 799    {
 800        public Dictionary<string, object> Props { get; set; } = new();
 801        public TypedPrincipal[] AllowedToDelegate { get; set; } = Array.Empty<TypedPrincipal>();
 802        public TypedPrincipal[] SidHistory { get; set; } = Array.Empty<TypedPrincipal>();
 803    }
 804
 805    public class ComputerProperties
 806    {
 807        public Dictionary<string, object> Props { get; set; } = new();
 808        public TypedPrincipal[] AllowedToDelegate { get; set; } = Array.Empty<TypedPrincipal>();
 809        public TypedPrincipal[] AllowedToAct { get; set; } = Array.Empty<TypedPrincipal>();
 810        public TypedPrincipal[] SidHistory { get; set; } = Array.Empty<TypedPrincipal>();
 811        public TypedPrincipal[] DumpSMSAPassword { get; set; } = Array.Empty<TypedPrincipal>();
 812    }
 813
 814    public class IssuancePolicyProperties
 815    {
 816        public Dictionary<string, object> Props { get; set; } = new();
 817        public TypedPrincipal GroupLink { get; set; } = new TypedPrincipal();
 818    }
 819}