I'm trying to get the root DirectoryEntry from LDAP so I can show a nice graphical tree view of it.
It all works beautifully under normal connections but I can't get it to work with SSL.
var root = this.checkBoxSSL.Checked
? new DirectoryEntry("LDAP://" + this.textBoxServer.Text,
this.textBoxUsername.Text,
this.textBoxPassword.Text,
AuthenticationTypes.SecureSocketsLayer)
: new DirectoryEntry("LDAP://" + this.textBoxServer.Text,
this.textBoxUsername.Text,
this.textBoxPassword.Text);
var dn = root.Properties["distinguishedName"].Value;
And so on...
But I get a "Server not operational" exception. It all seems to go down to the bind process. Based on internet research it might be a problem with the certificate and/or the Authentication method (NTLM, etc).
So how can I get a working DirectoryEntry over SSL?
I am open to alternative solutions, as long as I can retrieve all the LDAP Properties of the nodes I need. (Root, DC, OU, CN, Groups and Users)
EDIT: As it seems the problem comes down to the SSL certificate. We only have a self-signed cert atm. And this seems to be rejected by .NET by default. We're going to try this with a properly signed cert later on but it's likely I need to be able to handle selfsigned ones too.
This is where my knowledge about certificates has limits. I'm currently exploring a different code solution because it seems to be the only one that allows me to influence the whole certificate handling:
System.Security.Cryptography.X509Certificates.X509Certificate2 cert = new System.Security.Cryptography.X509Certificates.X509Certificate2();
cert.Import("..\\..\\test certificate.cer");
LdapConnection con = new LdapConnection("ip:636");
con.Credential = new NetworkCredential("un", "pw");
con.AuthType = AuthType.Ntlm;
con.SessionOptions.SecureSocketLayer = true;
con.SessionOptions.VerifyServerCertificate = new VerifyServerCertificateCallback((ldapcon, cer) => {
var cer2 = new System.Security.Cryptography.X509Certificates.X509Certificate2(cer);
StringBuilder strb = new StringBuilder();
strb.AppendFormat("{0} {1} matches: {2}\n", "Subject", cert.Subject, cert.Subject.Equals(cer2.Subject));
strb.AppendFormat("{0} {1} matches: {2}\n", "Cert Hash", cert.GetCertHashString(), Enumerable.SequenceEqual<byte>(cer.GetCertHash(), cert.GetCertHash()));
strb.AppendFormat("{0} matches: {2}\n", "Public Key", cert.GetPublicKeyString(), Enumerable.SequenceEqual<byte>(cer.GetPublicKey(), cert.GetPublicKey()));
strb.AppendFormat("{0}: {1}, {2}", "Verification", cert.Verify(), cer2.Verify());
var res = MessageBox.Show(strb.ToString(),
"Allow certificate?", MessageBoxButtons.YesNo);
return res == System.Windows.Forms.DialogResult.Yes;
});
con.Bind();
Essentially, if that VerifyServerCertificateCallback returns true then the connection succeeds, if it returns falls the connection fails with the same exception as with any other solution I've tried.
Curiously, neither installing the AD certificate or the root certificate of the AD controller didn't help the other solutions but it does change the result of the Verify() method.
What kind of checks do I have to perform on the certificate in the callback to maintain the sanctity of the SSL connection?
Secure your LDAP server connection between client and server application to encrypt the communication. In case of simple bind connection using SSL/TLS is recommended to secure the authentication as simple bind exposes the user crendetials in clear text.
The Lightweight Directory Access Protocol (LDAP /ˈɛldæp/) is an open, vendor-neutral, industry standard application protocol for accessing and maintaining distributed directory information services over an Internet Protocol (IP) network.
I suggest you to use PrincipalContext
from System.DirectoryServices.AccountManagement
. The initialization would look like this:
PrincipalContext context = new PrincipalContext(
ContextType.Domain, NAME_OF_THE_DOMAIN + ":636",
null,
ContextOptions.SecureSocketLayer | ContextOptions.Negotiate,
this.textBoxUsername.Text,
this.textBoxPassword.Text);
After that you can search for a UserPrincipal
and its DistinguishedName
:
string dn = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, this.textBoxUsername.Text).DistinguishedName;
If you want to itterate through the AD-tree just do something like this with the help of the PrincipalSearcher
:
using (var searcher = new PrincipalSearcher(new UserPrincipal(context)))
{
foreach (var result in searcher.FindAll())
{
DirectoryEntry de = result.GetUnderlyingObject() as DirectoryEntry;
//DO watherever you want
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With