2012-10-16 6 views
5

Ich habe eine Anwendung, die Daten aus einem Verzeichnisdienst mit dem typischen System.DirectoryServices.DirectoryEntry-Code repliziert. Ich habe jetzt die Anforderung, von Novell eDirectory mithilfe von SSL mit einem selbstsignierten Zertifikat zu replizieren. Ich vermute, dass der vorhandene Code mit einem gültigen Zertifikat funktionieren würde, das verifiziert werden könnte, oder vielleicht, wenn das selbstsignierte Zertifikat dem Schlüsselspeicher des lokalen Computers hinzugefügt wird. Damit es mit einem selbstsignierten Zertifikat jedoch sicher funktioniert, kann ich nur den System.DirectoryServices.Protocols-Namespace und die LdapConnection-Klasse verwenden, wodurch ich einen VerifyServerCertificate-Callback verbinden kann. Ich kann keine Möglichkeit finden, dasselbe Konzept auf eine DirectoryEntry-Instanz anzuwenden oder eine Verbindung mit einer LdapConnection-Instanz herzustellen und diese irgendwie in eine DirectoryEntry-Instanz zu "konvertieren". Vielleicht ist das nicht möglich, das möchte ich nur bestätigen. Alle anderen Gedanken willkommen.Rückruf für System.DirectoryServices.DirectoryEntry festlegen, um selbstsigniertes SSL-Zertifikat zu verarbeiten?

Die einzige relevante Verbindung, die ich gefunden habe, ist unter: http://www.codeproject.com/Articles/19097/eDirectory-Authentication-using-LdapConnection-and

Antwort

8

Dies ist eine phänomenale Frage.

Ich habe dieses Problem seit ein paar Tagen bekämpft, und ich habe endlich einen definitiven Beweis dafür, warum das DirectoryEntry-Objekt in diesem Szenario nicht funktioniert.

Dieser spezielle Ldap-Server (der auf LDAPS 636 ausgeführt wird) gibt auch sein eigenes selbstsigniertes Zertifikat aus. Verwendung LdapConnection (und Überwachen des Verkehrs über Wireshark), bemerkte ich einen Händedruck stattfindet, die nicht auftritt, wenn Directory Verwendung:

enter image description here

Die erste Sequenz ist die aus dem gesicherten LDAP-Server, die zweite Sequenz aus meine Maschine. Der Code, der die zweite Sequenz aufgefordert ist:

ldapConnection.SessionOptions.VerifyServerCertificate += delegate { return true; }; 

Es gibt andere Art und Weise zu „fake out“ der Rückruf, aber das, was ich habe mit.

Leider hat DirectoryEntry keine Option oder Methode zum Verifizieren eines selbstsignierten Zertifikats, daher tritt die Annahme des Zertifikats nie auf (zweite Sequenz), und die Verbindung kann nicht initialisiert werden.

Die einzige Möglichkeit, dies zu erreichen, ist die Verwendung von LdapConnection in Verbindung mit SearchRequest und SearchResponse. Dies ist, was ich bisher habe:

LdapConnection ldapConnection = new LdapConnection("xxx.xxx.xxx:636"); 

var networkCredential = new NetworkCredential("Hey", "There", "Guy"); 
ldapConnection.SessionOptions.SecureSocketLayer = true; 
ldapConnection.SessionOptions.VerifyServerCertificate += delegate { return true; }; 
ldapConnection.AuthType = AuthType.Negotiate; 
ldapConnection.Bind(networkCredential); 

SearchRequest request = new SearchRequest("DC=xxx,DC=xxx,DC=xxx", "(sAMAccountName=3074861)", SearchScope.Subtree); 
SearchResponse response = (SearchResponse)ldapConnection.SendRequest(request); 

if(response.Entries.Count == 1) 
{SearchResultEntry entry = response.Entries[0]; 
string DN = entry.DistinguishedName;} 

Von dort können Sie AD Eigenschaften aus dem Search und Verfahren entsprechend sammeln. Dies ist jedoch ein totaler Misserfolg, da die SearchRequest viel langsamer als die Verwendung des DirectoryEntry ist.

Hoffe, das hilft!

+0

Hallo. Danke für die Eingabe, sehr geschätzt. Ich komme auch zu der Schlussfolgerung, dass das ganze Problem viel zuverlässiger mit dem Protokoll-Namespace der niedrigeren Ebene angegangen werden könnte. Was ich bemerkt habe, ist, dass, wenn das selbstsignierte Zertifikat korrekt zum Zertifikatspeicher des lokalen Computers hinzugefügt wurde, ich den Callover von VerifyServerCertificate setzen kann, um das Cert erfolgreich zu validieren, etwa "delegate (LdapConnection-Verbindung, X509Certificate-Zertifikat) {return new X509Certificate2 (Zertifikat) .Verify()} ", aber ich habe immer noch keine Freude mit DirectoryEntry. – Andrew

+0

Ich habe auch bemerkt, dass ich in meinen Systemereignissen während fehlgeschlagener DirectoryEntry-Versuche Folgendes bekomme: "Das vom Remote-Server empfangene Zertifikat enthält nicht den erwarteten Namen. Es ist daher nicht möglich festzustellen, ob wir eine Verbindung zum richtigen Server herstellen. Der Servername, den wir erwartet haben, lautet MYSERVER.MYDOMAIN.CO.UK. Die SSL-Verbindungsanforderung ist fehlgeschlagen. Die angehängten Daten enthalten das Serverzertifikat. " Ich untersuche das jetzt, aber ich denke, die Antwort ist immer noch der Namespace von Protocols für maximales Vertrauen und reduzierte Umweltabhängigkeit. – Andrew

+0

Andrew, danke für den Kommentar. Es scheint, dass wir auf ein sehr ähnliches Ziel hinarbeiten. Dieser Fehler, der bei Systemereignissen auftritt, stimmt mit vielen Informationen überein, die ich im Internet gelesen habe, wem das Zertifikat ausgestellt wurde. Viele der Server, auf denen SSL ausgeführt wird, mit denen ich zu interagieren versuche, verfügen über ein Zertifikat, das an eine andere Domäne als die, an die ich in der LdapConnection binde, gesendet wird ... und dies führt sicherlich dazu, dass DirectoryEntry fehlschlägt. – X3074861X

2

Ich verspreche, dies wird mein letzter Beitrag zu dieser speziellen Frage sein. :)

Nach einer weiteren Woche der Forschung und Entwicklung, habe ich eine schöne Lösung für diese, und es hat bis jetzt außerordentlich gut für mich gearbeitet.

Der Ansatz ist etwas anders als meine erste Antwort, aber im Allgemeinen ist es das gleiche Konzept; Verwenden der LdapConnection, um die Validierung des Zertifikats zu erzwingen.

//I set my Domain, Filter, and Root-AutoDiscovery variables from the config file 
string Domain = config.LdapAuth.LdapDomain; 
string Filter = config.LdapAuth.LdapFilter; 
bool AutoRootDiscovery = Convert.ToBoolean(config.LdapAuth.LdapAutoRootDiscovery); 

//I start off by defining a string array for the attributes I want 
//to retrieve for the user, this is also defined in a config file. 
string[] AttributeList = config.LdapAuth.LdapPropertyList.Split('|'); 

//Delcare your Network Credential with Username, Password, and the Domain 
var credentials = new NetworkCredential(Username, Password, Domain); 

//Here I create my directory identifier and connection, since I'm working 
//with a host address, I set the 3rd parameter (IsFQDNS) to false 
var ldapidentifier = new LdapDirectoryIdentifier(ServerName, Port, false, false); 
var ldapconn = new LdapConnection(ldapidentifier, credentials); 

//This is still very important if the server has a self signed cert, a certificate 
//that has an invalid cert path, or hasn't been issued by a root certificate authority. 
ldapconn.SessionOptions.VerifyServerCertificate += delegate { return true; }; 

//I use a boolean to toggle weather or not I want to automatically find and query the absolute root. 
//If not, I'll just use the Domain value we already have from the config. 
if (AutoRootDiscovery) 
{ 
    var getRootRequest = new SearchRequest(string.Empty, "objectClass=*", SearchScope.Base, "rootDomainNamingContext"); 
    var rootResponse = (SearchResponse)ldapconn.SendRequest(getRootRequest); 
    Domain = rootResponse.Entries[0].Attributes["rootDomainNamingContext"][0].ToString(); 
} 

//This is the filter I've been using : (&(objectCategory=person)(objectClass=user)(&(sAMAccountName={{UserName}}))) 
string ldapFilter = Filter.Replace("{{UserName}}", UserName); 

//Now we can start building our search request 
var getUserRequest = new SearchRequest(Domain, ldapFilter, SearchScope.Subtree, AttributeList); 

//I only want one entry, so I set the size limit to one 
getUserRequest.SizeLimit = 1; 

//This is absolutely crucial in getting the request speed we need (milliseconds), as 
//setting the DomainScope will suppress any refferal creation from happening during the search 
SearchOptionsControl SearchControl = new SearchOptionsControl(SearchOption.DomainScope); 
getUserRequest.Controls.Add(SearchControl); 

//This happens incredibly fast, even with massive Active Directory structures 
var userResponse = (SearchResponse)ldapconn.SendRequest(getUserRequest); 

//Now, I have an object that operates very similarly to DirectoryEntry, mission accomplished 
SearchResultEntry ResultEntry = userResponse.Entries[0]; 

Das andere, was ich wollte hier zu beachten ist, dass SearchResultEntry Benutzer zurückkehren wird „Attribute“ anstelle von „Eigenschaften“.

Attribute werden als Bytearrays zurückgegeben, daher müssen Sie diese codieren, um die Zeichenfolgendarstellung zu erhalten. Zum Glück enthält System.Text.Encoding eine native ASCIIEncoding-Klasse, die das sehr einfach handhaben kann.

string PropValue = ASCIIEncoding.ASCII.GetString(PropertyValueByteArray); 

Und das war's! Sehr glücklich, das endlich herausgefunden zu haben.

Prost!

+0

Danke dafür, ich habe mich auch entschieden, die Einheit System.DirectoryServices.Protocols am Ende neu zu konstruieren. Zugegeben, es ist ein wenig prozedural, aber mit Win32 LDAP-Bibliotheken gearbeitet, bevor es dem Metall näher kommt. Und ich bin sicherer, dass der Code an zukünftige Anforderungen angepasst werden kann als Code, der ADSI-basierte System.DirectoryServices verwendet. – Andrew

0

Ich habe unten Code verwendet, um mit Idaps mit DirectoryEntry zu verbinden.

Was ich in meinem scenerio verstanden wird Directory funktioniert nicht, wenn ldaps in Server-Pfad angegeben ist oder Authentifizierungstyp wird als „AuthenticationTypes.SecureSocketsLayer“ erwähnt, aber nur dann, wenn ldaps ist Port am Ende des Servernamens es Arbeit erwähnt . Nach einem Blick auf Wireshark Log sehe ich einen Handshake wie in der obigen Post erwähnt.

Handshake: enter image description here

Code:

public static SearchResultCollection GetADUsers() 
    { 
     try 
     { 
      List<Users> lstADUsers = new List<Users>(); 
      DirectoryEntry searchRoot = new DirectoryEntry("LDAP://adserver.local:636", "username", "password"); 
      DirectorySearcher search = new DirectorySearcher(searchRoot); 
      search.PropertiesToLoad.Add("samaccountname"); 
      SearchResult result; 
      SearchResultCollection resultCol = search.FindAll(); 
      Console.WriteLine("Record count " + resultCol.Count); 
      return resultCol; 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine("exception" + ex.Message); 
      return null; 
     } 
    } 
Verwandte Themen