I'm trying to use the .NET 3.5 System.DirectoryServices.AccountManagement
namespace to validate user credentials against our Active Directory LDAP server over an SSL encrypted LDAP connection. Here's the sample code:
using (var pc = new PrincipalContext(ContextType.Domain, "sd.example.com:389", "DC=sd,DC=example,DC=com", ContextOptions.Negotiate))
{
return pc.ValidateCredentials(_username, _password);
}
This code works fine over unsecured LDAP (port 389), however I'd rather not transmit a user/pass combination in clear text. But when I change to LDAP + SSL (port 636), I get the following exception:
System.DirectoryServices.Protocols.DirectoryOperationException: The server cannot handle directory requests.
at System.DirectoryServices.Protocols.ErrorChecking.CheckAndSetLdapError(Int32 error)
at System.DirectoryServices.Protocols.LdapSessionOptions.FastConcurrentBind()
at System.DirectoryServices.AccountManagement.CredentialValidator.BindLdap(NetworkCredential creds, ContextOptions contextOptions)
at System.DirectoryServices.AccountManagement.CredentialValidator.Validate(String userName, String password)
at System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials(String userName, String password)
at (my code)
Port 636 works for other activities, such as looking up non-password information for that LDAP/AD entry...
UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, _username)
...so I know it's not my LDAP server's SSL setup, since it works over SSL for other lookups.
Has anyone gotten the ValidateCredentials(...)
call to work over SSL? Can you explain how? Or is there another/better way to securely validate AD/LDAP credentials?
I was able to validate credentials using the
System.DirectoryServices.Protocols
namespace, thanks to a co-worker. Here's the code:I'm not thrilled with using a try/catch block to control decisioning logic, but it's what works. :/
For me, the ValidateCredentials method works just fine. The problem, I found, was on the server hosting the AD (I'm using AD LDS). You needed to associate the server certificate with the AD instance. So if your instance was called 'MyAD' (or ActiveDirectoryWebService), you needed to open up the MMC, snap in the 'Certificates' module, select 'Service Account' and then select 'MyAD' from the list. From there you can add the SSL certificate into the 'MyAD' Personal store. This finally kicked the SSL processing into gear.
I suspect, from what I know of the LdapConnection method and the fact that you omitted the callback function, that you are not validating your server certificate. It's a messy job and ValidateCredentials does it for free. Probably not a big deal, but a security hole none-the-less.
Maybe this is another way. There's nothing unusual in validate credentials. The ContextOptions must set properly.
Default value:
Add Ssl:
ContextOptions.Negotiate or ContextOptions.SimpleBind is required. Or whatever your server need to perform authentication. ContextOptions only supports OR bit to bit.
You could try also set the ContextOptions directly this way in ValidateCredentials method.
Or
I know this is old, but for anybody running into this again:
PrincipalContext.ValidateCredentials(...)
, by default, tries to open an SSL connection (ldap_init(NULL, 636)
) followed by setting the optionLDAP_OPT_FAST_CONCURRENT_BIND
.If a (trusted?) client certificate is present, however, the LDAP connection is implicitly bound and fast bind cannot be enabled anymore. PrincipalContext doesn't consider this case and fails with an unexpected
DirectoryOperationException
.Workaround: To support SSL where possible, but have a fallback, call
ValidateCredentials(...)
with default options first (i.e. no options). If this fails with theDirectoryOperationException
, try again by specifying theContextOptions
(Negotiate | Sealing | Signing), which is whatValidateCredentials
internally does for the expectedLdapException
anyway.