C# - Search AD entry by ObjectSid using Novell Directory Ldap Nuget package
Tags: csharp, dotnetcore, kubernetes, ldap
Published: 2023-03-13 21:03:54

**SID stands for security identifier, a unique string that Windows Server automatically assigns to each computer, user and group in order to mark and clearly distinguish them.**

Windows SID Format :
SIDs always follow the same structure, with values separated by dashes:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w7rjov714wxv9po0fcj5.png)

**Novell Directory Ldap**

Novell Directory Ldap nuget package allows you to connect to the active directory in order to perform CRUD actions on all AD objects, among other Users Groups and computers. it support both platforms windows and linux, so even if you are under kubernetes  clusters on docker containers you will be able to manage AD objects.

**C# code practical case**

This exemple is based on a project that was created based on .Net 6 under Visual studio 2022 and with a docker support to manage Ad Objects within a linux docker container.
The nuget package version that was used are the following : 
```xml
<PackageReference Include="Novell.Directory.Ldap.NETStandard" Version="3.6.0" />
```

When we will process a search within the AD for a dedicated entry we will use a string ObjectSid instead of a bytes value. To do that we will create a helper method that will return the string value based on the input ObjectSid bytes value as bellow : 


**1. Add Helper method to convert ObjectSID Byte value in string format**
```cs
 public static string ConvertSidByteValueToStringValue(Byte[] sidBytes)
        {
            short sSubAuthorityCount = 0;
            StringBuilder strSid = new StringBuilder();
            strSid.Append("S-");
            try
            {
                // Add SID revision.
                strSid.Append(sidBytes[0].ToString());

                sSubAuthorityCount = Convert.ToInt16(sidBytes[1]);

                // Next six bytes are SID authority value.
                if (sidBytes[2] != 0 || sidBytes[3] != 0)
                {
                    string strAuth = String.Format("0x{0:2x}{1:2x}{2:2x}{3:2x}{4:2x}{5:2x}",
                                   (Int16)sidBytes[2],
                                   (Int16)sidBytes[3],
                                   (Int16)sidBytes[4],
                                   (Int16)sidBytes[5],
                                   (Int16)sidBytes[6],
                                   (Int16)sidBytes[7]);
                    strSid.Append("-");
                    strSid.Append(strAuth);
                }
                else
                {
                    Int64 iVal = sidBytes[7] +
                         (sidBytes[6] << 8) +
                         (sidBytes[5] << 16) +
                         (sidBytes[4] << 24);
                    strSid.Append("-");
                    strSid.Append(iVal.ToString());
                }

                // Get sub authority count...
                int idxAuth = 0;
                for (int i = 0; i < sSubAuthorityCount; i++)
                {
                    idxAuth = 8 + i * 4;
                    UInt32 iSubAuth = BitConverter.ToUInt32(sidBytes, idxAuth);
                    strSid.Append("-");
                    strSid.Append(iSubAuth.ToString());
                }
            }
            catch (Exception ex)
            {
                Trace.TraceWarning(ex.Message);
                throw;
            }

            return strSid.ToString();
        }
```

This method will get **ObjectSid** information parts from the array of Bytes input value as bellow :

```cs
byte[0]      - Revision Level
byte[1]      - count of Sub-Authorities
byte[2 - 7]  - 48 bit Authority(big-endian)
byte[8 +]    - n Sub-Authorities, 32 bits 
```

And after that we return a String **ObjectSID** on the format bellow : 
```cs
S-{Revision}-{Authority}-{ SubAuthority1}-{ SubAuthority2}...-{ SubAuthorityN}
```

**2. Call Active Directory to retrieve User informations based on objectSid string value**

**Adding Novell directive**
```c#
using Novell.Directory.Ldap;
.....
```

**Adding the search method to get the User entry object**

```c#
/// Exemple of Dn Value  : OU=User,OU=Accounts,DC=USA,DC=NY
        /// Exemple of objectSidStringValue Value  : S-1-5-15-420314761-778715008-4547327-1845947
        /// Exemple of objectCategory Value  : User or Group or Computer
        /// Exemple of OutputProps Values : "description","lastLogon","email","name", "login"
        public List<Attributes<string, string>>? GetLdapEntryByObjectSid(string Dn, string objectSidStringValue, string objectCategory, string[] OutputProps)
        {
            var ldapConnectionOptions = new LdapConnectionOptions();
            ldapConnectionOptions.UseSsl();
            var ldapConx = new LdapConnection(ldapConnectionOptions);

            List<Attributes<string, string>> listAttributes = new List<Attributes<string, string>>();

            string Filter = $"(&(objectSid={objectSidStringValue})(objectCategory={objectCategory}))";
            

            if (!string.IsNullOrEmpty(Filter))
            {
                var searchQuery = ldapConx.Search(Dn, Novell.Directory.Ldap.LdapConnection.ScopeSub, Filter, OutputProps, false);
                if (searchQuery != null)
                {
                    while (searchQuery.HasMore())
                    {
                        LdapEntry nextEntry = null;
                            nextEntry = searchQuery.Next();

                            //SID Case 
                            if (OutputProps.Contains("objectSid"))
                            {
                                //Get Sid Property Bytes value to be converted into string clear value
                                var objectSid = nextEntry.GetAttributeSet().FirstOrDefault(e => e.Key.Equals("objectSid"));
                                if (objectSid.Value != null)
                                {
                                    var sidStringFormat = ConvertSidByteValueToStringValue(objectSid.Value.ByteValue);
                                    listAttributes.Add(new Attributes<string, string>(objectSid.Key, sidStringFormat));
                                }
                            }

                            listAttributes.AddRange(nextEntry.GetAttributeSet()
                                .Where(e => !e.Key.Equals("objectSid"))
                                .Select(e => new Attributes<string, string>(e.Key, e.Value.StringValue))
                                .ToList());                       
                    }
                }
            }

            return listAttributes;
        }

```
The **GetLdapEntryByObjectSid** method will process a **searchQuery** within ldap based on the **objectSid** filter and also the objectCategory in this example **User** filter. As mentioned the objectSid within Active directory are persisted as a byte so we will call our Helper method to convert it into a string value: 
```c#
 var sidStringFormat = ConvertSidByteValueToStringValue(objectSid.Value.ByteValue);
```
The returned list of properties will contains all properties as string readable values, so we can use them also to process a new search based on other property.


Links : 
[Novell Directory - GitHub repository](https://github.com/dsbenghe/Novell.Directory.Ldap.NETStandard)
[Novell Directory - Nuget package]
(https://www.nuget.org/packages/Novell.Directory.Ldap.NETStandard/)