Reputation: 1948
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?
Upvotes: 10
Views: 48295
Reputation: 2203
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 iterate 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
}
}
Upvotes: 7
Reputation: 301
Brian Desmond was almost there. You need to set 2 flags in DirectoryEntry AuthenticationType:
AuthenticationTypes.SecureSocketsLayer | AuthenticationTypes.Secure
example:
new DirectoryEntry("LDAP://" + this.textBoxServer.Text + ":636",
this.textBoxUsername.Text,
this.textBoxPassword.Text,
AuthenticationTypes.SecureSocketsLayer | AuthenticationTypes.Secure)
Upvotes: 7
Reputation: 311
At first, you should make sure you account and password is avaliable. use ldp.exe tool to check the account is avaliable.
then maybe you can try like below, please use LDAP:// not LDAPS://
LdapConnection conn = new LdapConnection("xx1.bb.aa.com:636");
var op = conn.SessionOptions;
op.ProtocolVersion = 3;
op.SecureSocketLayer = true;
op.VerifyServerCertificate += delegate { return true; };
conn.AuthType = AuthType.Negotiate;
var cred = new NetworkCredential("accountname", "password");
//conn.Credential = cred;
try
{
conn.Bind(cred);
if (op.SecureSocketLayer)
{
Console.WriteLine("SSL for encryption is enabled - SSL information:");
}
}
catch (Exception ex)
{
throw;
}
OR:
DirectoryEntry directoryEntry = new DirectoryEntry("LDAP://xx1.bb.aa.com:636",
"ldapsusername", "password", AuthenticationTypes.SecureSocketsLayer);
//directoryEntry.Options
DirectorySearcher searcher = new DirectorySearcher(directoryEntry)
{
PageSize = int.MaxValue,
Filter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=hp.wang))"
};
searcher.PropertiesToLoad.Add("sn");
var result = searcher.FindOne();
if (result == null)
{
return; // Or whatever you need to do in this case
}
string surname;
if (result.Properties.Contains("sn"))
{
surname = result.Properties["sn"][0].ToString();
}
Upvotes: 5
Reputation: 4503
I believe all you need to do is tweak this snippet to target the correct port (generally 636):
new DirectoryEntry("LDAP://" + this.textBoxServer.Text + ":636",
this.textBoxUsername.Text,
this.textBoxPassword.Text,
AuthenticationTypes.SecureSocketsLayer)
Upvotes: 2