Search code examples
c#active-directorypasswordsldappassword-policy

Verify password complexity against Active Directory in C#


I am writing an installer that installs SQL, beforehand a user is prompted to enter the SA username/password which will be created for them. When SQL installs it verifies this password against the Active Directory policy and will fail if it doesnt match.

What I want to do is verify the password input by the user is valid before proceeding to install SQL.

How can I validate a password is correct against Active Directory rules?

Note I do not have a login to verify as per this answer, but simply a password to verify.

I am currently trying this, but writing "password" which I know is not allowed doesnt throw an exception

try
{
    System.DirectoryServices.DirectoryEntry localMachine = new System.DirectoryServices.DirectoryEntry("WinNT://" + Environment.MachineName);
    ListPasswordPolicyInfo(Environment.MachineName);
    System.DirectoryServices.DirectoryEntry newUser = localMachine.Children.Add("localuser", "user");
    newUser.Invoke("SetPassword", new object[] { "3l!teP@$$w0RDz" });
    newUser.Invoke("SetPassword", new object[] { "password" });
    //newUser.CommitChanges();
    //Console.WriteLine(newUser.Guid.ToString());
    localMachine.Close();
    newUser.Close();
}
catch(Exception e)
{
    Console.WriteLine(e.Message);
}

Solution

  • After a lot of pain I have found the C# solution to this using NetValidatePasswordPolicy. Use the supporting structs off of PInvoke and the following code

    public static NET_API_STATUS ValidatePassword(string password)
    {
        var outputArgs = new NET_VALIDATE_OUTPUT_ARG();
        var inputArgs = new NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG();
    
        IntPtr inputPointer = IntPtr.Zero;
        IntPtr outputPointer = IntPtr.Zero;
    
        try
        {
            inputArgs.PasswordMatched = true;
            inputArgs.ClearPassword = Marshal.StringToBSTR(password);
    
            // If using a secure string
            ////inputArgs.ClearPassword = Marshal.SecureStringToBSTR(secureStringPassword);
    
            inputPointer = Marshal.AllocHGlobal(Marshal.SizeOf(inputArgs));
            Marshal.StructureToPtr(inputArgs, inputPointer, false);
    
            NET_API_STATUS status = NetValidatePasswordPolicy(System.Environment.MachineName, IntPtr.Zero, NET_VALIDATE_PASSWORD_TYPE.NetValidatePasswordChange, inputPointer, ref outputPointer);
    
            if (status == NET_API_STATUS.NERR_Success)
            {
                outputArgs = (NET_VALIDATE_OUTPUT_ARG)Marshal.PtrToStructure(outputPointer, typeof(NET_VALIDATE_OUTPUT_ARG));
    
                if (outputArgs.ValidationStatus == NET_API_STATUS.NERR_Success)
                {
                    // Ok
                }
    
                return outputArgs.ValidationStatus;
            }
            else
            {
                return status;
            }
        }
        finally
        {
            if (outputPointer != IntPtr.Zero)
            {
                NetValidatePasswordPolicyFree(ref outputPointer);
            }
    
            if (inputArgs.ClearPassword != IntPtr.Zero)
            {
                Marshal.ZeroFreeBSTR(inputArgs.ClearPassword);
            }
    
            if (inputPointer != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(inputPointer);
            }
        }
    }