C# LDAP user change password failed. Is SSL connection must? - c#

I am writing an ASP.NET Core 5 Web API (platform independent) to change the LDAP user password. I'm using the library Novell.Directory.Ldap.
This is my code:
var ldapHost = "192/168.*.*";
var loginDN = "CN=something,DC=something"; //loginDn of the user itself or admin
var opassword = "Abcd#11111111"; //oldpassword
var npassword = "Xyzw#22222222"; //newpassword
npassword = '"' + npassword + '"';
LdapConnection conn = new LdapConnection();
Console.WriteLine("Connecting to:" + ldapHost);
conn.Connect(ldapHost, LdapConnection.DefaultPort);
conn.Bind(loginDN, opassword);
LdapModification[] modifications = new LdapModification[2];
LdapAttribute deletePassword = new LdapAttribute("userPassword", opassword);
modifications[0] = new LdapModification(LdapModification.Delete, deletePassword);
LdapAttribute addPassword = new LdapAttribute("userPassword", npassword);
modifications[1] = new LdapModification(LdapModification.Add, addPassword);
conn.Modify(loginDN, modifications);
I am testing this code for a Windows AD domain as well as Linux OpenLDAP. Both LDAP server's users have the attribute property userPassword present.
When I run this code LdapModification.ADD throws an error that No such attribute userPassword. when I try to find the solution I get people using attribute unicodePwd, but it needs an SSL connection.
Is the SSL connection a must for AD domains and Open LDAP? Or how else to solve the above error? Please help.

While AD has a userPassword attribute, it's just a pointer to the unicodePwd attribute, which is the real password attribute. But userPassword doesn't always work, as described in the documentation. For Active Directory, you're better off using unicodePwd directly.
The documentation for unicodePwd says that you require either SSL or SASL encryption to set the attribute. SASL would usually be done with Kerberos. If you were using DirectoryEntry, that's easily done by specifying AuthenticationTypes.Sealing. With Novell.Directory.Ldap, I don't know if that's possible, and this open issue suggests that it isn't (yet).
Unless you're willing to switch to using Sytem.DirectoryServices (which, in .NET Core, would lock you into running your app on Windows only), then I think you are stuck requiring SSL.
The unicodePwd attribute also requires a very specific format. The documentation says:
the DC requires that the password value be specified in a UTF-16 encoded Unicode string containing the password surrounded by quotation marks, which has been BER-encoded as an octet string per the Object(Replica-Link) syntax.
In C#, that's easily done like this:
Encoding.Unicode.GetBytes("\"NewPassword\"")

Related

LDAP Change password: Exception from HRESULT: 0x80070547

I am trying to run my password change application from a non domain joined machine. The code works fine when run from domain joined machine. So now, I am connecting to the AD with direct LDAP connection via SSL. After changepassword method is invoked, I am getting an error:
Configuration information could not be read from the domain controller, either because the machine is unavailable, or access has been denied. (Exception from HRESULT: 0x80070547).
I am making the connection and running the application using a service account with permission to change user passwords.
string adminUser = Domain + #"\" + AdminUserName;
string adminPass = AdminUserPassword;
string ldapString = LDAPString;
DirectoryEntry de = new DirectoryEntry(ldapString, adminUser, adminPass, AuthenticationTypes.Secure);
DirectorySearcher deSearch = new DirectorySearcher(de) { SearchRoot = de, Filter = "(&(objectCategory=user)(cn=" + userName + "))" };
SearchResult result = deSearch.FindOne();
if (result != null)
{
var adContext = new PrincipalContext(ContextType.Domain);
currentdc = adContext.ConnectedServer;
DirectoryEntry userEntry = result.GetDirectoryEntry();
if (userEntry != null)
{
userEntry.Invoke("ChangePassword", new object[] { OldPassword, NewPassword });
}
}
Invoking ChangePassword, calls IADsUser::ChangePassword. That documentation says it works much the same as IADsUser::SetPassword. That documentation has more information. Really, only the first method would work when you're running this from outside the domain:
First, the LDAP provider attempts to use LDAP over a 128-bit SSL connection. For LDAP SSL to operate successfully, the LDAP server must have the appropriate server authentication certificate installed and the clients running the ADSI code must trust the authority that issued those certificates. Both the server and the client must support 128-bit encryption.
I assume your LDAPString is in the format LDAP://example.com:636 (the :636 being the important part). If you can read data like that, then the SSL certificate is trusted. So that's good.
The only maybe missing piece could be 128-bit encryption? Check the certificate and see if it's maybe using less than 128-bit. Although I'd be surprised if it did.
This answer has a short snippet of code that you can use to download a certificate from any site: https://stackoverflow.com/a/22251597/1202807
Just use "https://example.com:636" as the "website".
There is also this:
In Active Directory, the caller must have the Change Password extended control access right to change the password with this method.
You should make sure that the user account you are authenticating to LDAP with does have the Change Password permission on the account you are trying to update. In our environment, Everyone has the Change Password permission (since you still need to provide the old password to do it). I think that's the default, but it's worth checking.

C# LDAP SSL Logon issue on F5 VIP name when LdapEnforceChannelBinding=1 or 2

I have C# Windows Form application to test the LDAP SSL authentication.
Here is the code. First, I made a function.
using System.DirectoryServices;
private bool VerifyDomainUserUsignLDAP(string UserName, string Password, string Domain,string mode, out string message)
{
bool retVal = false;
message = null;
DirectoryEntry de;
try
{ if (mode =="Plain")
//plain mode
de = new DirectoryEntry(Domain, UserName, Password);
else
//SSL mode
de = new DirectoryEntry(Domain, UserName, Password,
AuthenticationTypes.Secure | AuthenticationTypes.SecureSocketsLayer);
DirectorySearcher ds = new DirectorySearcher(de);
SearchResult sr= ds.FindOne();
lblResult.Text = "Authentication Passed! " + sr.ToString();
retVal = true;
}
catch (Exception ex)
{
retVal = false;
lblResult.Text = ex.Message;
}
return retVal;
}
My problem is the invoke.
Share with some background first.
We have multiple domain control servers (Windows). dcserver001.mydomain.com is one of them. (of course, we have dcserver002.mydomain.com, dcserver003.mydomain.com, etc). each server provides LDAPS service.
And we created a VIP name ldap.mydomain.com in F5 (Load balance), we put all above dc servers into the Load balance. All DC servers are Windows servers.
Days before, if I use following line to invoke above function for LDAP authenticate on the VIP name - ldap.mydomain.com.
For e.g.
VerifyDomainUserUsignLDAP("mydomain\\myuserid", "mypassword",
#"LDAP://ldap.mydomain.com", "SSL" ,out Message);
It always worked fine and the user was authenticated.
However, some days before, our LDAP service team made a registry change (LdapEnforceChannelBinding) on each ldap servers to
enhance the security based on MS suggestion.
In short, they changed following key value from 0 to 2
Path: HKEY_LOCAL_MACHINE/System/CurrentControlSet/Services/NTDS/Parameters
Key: LdapEnforceChannelBinding
Here is detail page about the setting on MS web site.
Use the LdapEnforceChannelBinding registry entry to make LDAP authentication over SSL/TLS more secure
https://support.microsoft.com/en-hk/help/4034879/how-to-add-the-ldapenforcechannelbinding-registry-entry
After that, I noticed my above function stop working.
i.e. if I use same line to invoke above function for LDAP authenticate.
For e.g.
VerifyDomainUserUsignLDAP("mydomain\\myuserid", "mypassword",
#"LDAP://ldap.mydomain.com", "SSL" ,out Message);
It always returned exception "Logon failure: unknown user name or password."
(I promise password and user name were correct.)
Then, I did further investigation.
I tried to use following line to invoke above function for LDAP authenticate on any individual dc server, e.g.
dcserver001.mydomain.com.
VerifyDomainUserUsignLDAP("mydomain\\myuserid", "mypassword",
#"LDAP://dcserver001.mydomain.com", "SSL" ,out Message);
It worked fine as well.
I actually tested all individual dc servers one by one, thwy were all working.
So, it looks like the ldap request with same invoke parameters works well on the individual dc server, but it doesn't work on the VIP name.
Then, I asked the ldap server team to rollback to LdapEnforceChannelBinding change to value 0. Then, I re-tested ldap against both individual server and VIP name, both worked.
I checked with our metwork team and got got some more information as follwoing.
They said this won't work with LDAPS VIPs because the SSL channel from client is terminated on F5, and reestablished to DC.
the reason why it works directly to the dc is because its one continuous packet.
The update addresses this vulnerability by incorporating support for Extended Protection for Authentication security feature, which allows the LDAP server to detect and block such forwarded authentication requests once enabled.
What I need help is - is there anyone here encountered the similar ldap ssl logon issue against F5 VIP and with the LdapEnforceChannelBinding registry value = 1 or 2?
If LdapEnforceChannelBinding registry value = 1 or 2 on the LDAP servers, what changes need to be done to resolve above LDAPS logon issue?
Thanks a lot!
Jun

Authenticating to AD using sAMAccountName format using PrincipleContext

I am implementing AD authentication for an offline application.
My original code did the following:
var validAuth = false;
using (var context = new System.DirectoryServices.AccountManagement.PrincipalContext(System.DirectoryServices.AccountManagement.ContextType.Domain))
{
validAuth = context.ValidateCredentials(_viewModel.Username, txtPassword.Password);
}
However, during testing it was noticed that this caused account lockouts in half the number of attempts of the AD Group Policy - so say the policy was set to 4 attempts before lockout, the user would be locked out in 2.
I googled and found this article on MSDN and the TL;DR is this:
It sounds that bad password count increase by 2 if you use UPN format (Domain#sAMAccountName), however, the count increase 1 every time if you use sAMAccountName format (Domain\sAMAccountName).
In light of this I changed my code to this:
var validAuth = false;
using (var context = new System.DirectoryServices.AccountManagement.PrincipalContext(System.DirectoryServices.AccountManagement.ContextType.Domain))
{
var usernameToAuth = string.Format("{0}\\{1}", Environment.UserDomainName, _viewModel.Username);
validAuth = context.ValidateCredentials(usernameToAuth, txtPassword.Password);
}
But this now fails to authenticate regardless of input. If I change it to use the old style UPN format of user#domain it authenticates fine - but obviously that is using up two authentication requests.
The MSDN post says to use the sAMAccountName format as a work around but I am struggling to work out how to do this. My original code also didn't explicitly use the old UPN format - I just passed the User Name directly to the ValidateCredentials method (no # symbol anywhere to be seen) so does this method use the old UPN method first?
Any advice please - I don't particularly want to half the bad log on attempts our users can have.
I used the domain specification in the PrincipalContext constructor, specified in this post, like that:
public static bool IsAuthenticated(string username_, string password_)
{
using (var pc = new PrincipalContext(ContextType.Domain, DomainManager.DomainName))
return pc.ValidateCredentials(username_, password_);
}
In my case, I use the System.DirectoryServices.ActiveDirectory.Domain and System.DirectoryServices.ActiveDirectory.DomainController to get this DomainManager.DomainName values.

Active directory and LDAP libraries

I am trying to authenticate users to active directory with the Novell.Directory.Ldap libraries found in Mono. I know there is better ways than below, but given that I'm confined to Mono, these are the only supported routines as best I can see.
Using the .NET libraries, I can authenticate a user with their samAccountName.
using (DirectoryEntry de = new DirectoryEntry())
{
de.Username = username;
de.Password = password;
de.Path = string.Format("LDAP://{0}/{1}", ADHostname, DefaultNamingContext);
de.AuthenticationType = AuthenticationTypes.Secure;
using (DirectorySearcher deSearch = new DirectorySearcher())
{
deSearch.SearchRoot = de;
deSearch.PropertiesToLoad.Add("cn");
deSearch.Filter = "(&(objectCatagory=person))";
deSearch.FindOne();
}
}
but this fails with invalid credentials if it's running inside mono. The only way to make it work is by specifying the UPN for username:
de.Username = "foo#my.domain.com";
The problem is, UPN is not a required attribute for AD. So how can I authenticate a user with just their username?
I see a post about one way to do it: Authenticating user using LDAP from PHP
But, it's a chicken and egg problem. How do I bind to search for the users DN so I can bind, if I can't bind as an authenticated user to begin with.
Thank you for any help you can give.
Usually you get an account for your application to allow the search for other user DN's. Traditionally this was done using an anonymous bind, but nowadays that is usually blocked for security reasons.
Therefore get a service account with a known DN and password. Bind as that service account, do your search, then bind as the users DN that you found via the search.

C#: How to connect to Active Directory with SSL enabled?

The project I am working on will integrate with the customers Active Directory in order to authenticate users. I have been trying to write some code that will retrieve a users password and I understand that Active Directory will only expose the relevant properties over a SSL connection on port 636.
The following code connects programmatically without using SSL but then I can't see the password properties:
static void Main(string[] args)
{
DirectoryEntry entry = new DirectoryEntry(#"LDAP://<IP>/CN=LDAP Test,CN=Users,DC=customer,DC=com");
entry.AuthenticationType = AuthenticationTypes.None;
entry.Username = "CN=LDAP Test,CN=Users,DC=customer,DC=com";
entry.Password = "<password>";
if (entry != null)
{
foreach (Object propName in entry.Properties.PropertyNames)
{
Console.WriteLine((String)propName);
}
}
}
When I change the code to use SSL I get an exception stating ;Unknown error (0x80005000)'.
I have enabled SSL on the server hosting Active Directory, installed a Microsoft CA on the same server and obtained a certificate from the CA.
I can connect to the Active Directory over SSL using Apache Directory Studio but that does not show the password properties.
The following code shows what I have been trying to use to connect using SSL:
static void Main(string[] args)
{
DirectoryEntry entry = new DirectoryEntry(#"LDAPS://<IP>:636/CN=LDAP Test,CN=Users,DC=customer,DC=com");
entry.AuthenticationType = AuthenticationTypes.SecureSocketsLayer;
entry.Username = "CN=LDAP Test,CN=Users,DC=customer,DC=com";
entry.Password = "<password>";
if (entry != null)
{
foreach (Object propName in entry.Properties.PropertyNames)
{
Console.WriteLine((String)propName);
}
}
}
I'm not sure where to go with this and some assistance would be greatly appreciated.
I have been trying to write some code
that will retrieve a users password...
This is unrelated to your SSL problem, but I don't think retrieving a user's password from Active Directory is possible. It only stores a hash and that's why you aren't receiving any kind of "password" property when querying the user's properties.
Updated Answer
After reading your comment, it appears you're looking for the unicodePwd attribute which contains the security hash. According to the MSDN information, writing to that attribute requires the special SSL connection but you still won't be able to read it because it's a write-only attribute.
Specifically from MSDN:
The unicodePwd attribute is never returned by an LDAP search.
Here's also a forum post that I found that seems to say the same thing:
The users' password is stored in the
Active Directory on a user object in
the unicodePwd attribute. This
attribute can be written under
restricted conditions, but it cannot
be read due to security reasons.
(Source)
Try adding the server's certificate and root certificate to your local store. The easiest way to do this is to use IE to connect to https://your.domain.contoller:636. Then click through all the certificate screens and add them to your store.

Categories

Resources