2012-10-16 9 views
5

Possiedo un'applicazione che replica i dati da un servizio di directory utilizzando il tipico codice System.DirectoryServices.DirectoryEntry. Ora ho il requisito di replicare da Novell eDirectory usando SSL con un certificato autofirmato. Sospetto che il codice esistente possa funzionare con un certificato valido che potrebbe essere verificato, o forse se il certificato autofirmato viene aggiunto al keystore della macchina locale. Tuttavia, per farlo funzionare con un certificato autofirmato, l'unica soluzione che riesco a trovare è utilizzare lo spazio dei nomi System.DirectoryServices.Protocols e la classe LdapConnection, in base al quale è possibile effettuare il wire up di un callback VerifyServerCertificate. Non riesco a trovare alcun modo di applicare lo stesso concetto a un'istanza DirectoryEntry o di connettersi con un'istanza LdapConnection e in qualche modo "convertirlo" in un'istanza DirectoryEntry. Forse non è possibile, vorrei solo confermarlo davvero. Qualsiasi altro pensiero benvenuto.Impostare la richiamata per System.DirectoryServices.DirectoryEntry per gestire il certificato SSL autofirmato?

L'unico legame pertinente che ho trovato e ': http://www.codeproject.com/Articles/19097/eDirectory-Authentication-using-LdapConnection-and

risposta

8

Questa è una domanda fenomenale.

Ho combattuto lo stesso problema per alcuni giorni e ho finalmente ottenuto alcune prove definitive sul perché l'oggetto DirectoryEntry non funzionerà in questo scenario.

Questo particolare server Ldap (in esecuzione su LDAPS 636) rilascia anche il proprio certificato autofirmato. Utilizzando LdapConnection (e il monitoraggio del traffico tramite Wireshark), ho notato una stretta di mano in atto che non si verifica quando si utilizza DirectoryEntry:

enter image description here

La prima sequenza è la dal server LDAP protetto, la seconda sequenza è da la mia macchina Il codice che richiede la seconda sequenza è:

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

Non ci sono altri modo per "falso fuori" la richiamata, ma questo quello che ho usato.

Sfortunatamente, DirectoryEntry non ha un'opzione o un metodo per verificare un certificato autofirmato, quindi l'accettazione del certificato non avviene mai (seconda sequenza) e la connessione non riesce a inizializzarsi.

L'unico modo possibile per eseguire ciò è utilizzare LdapConnection, in combinazione con un SearchRequest e SearchResponse. Questo è quello che ho ottenuto finora:

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;} 

Da lì è possibile raccogliere Proprietà AD da RicercaRisposta e procedere di conseguenza. Questo è un vero peccato, perché SearchRequest sembra essere molto più lento di usare DirectoryEntry.

Spero che questo aiuti!

+0

Hi. Grazie per l'input, molto apprezzato. Sto anche arrivando alla conclusione che l'intero problema potrebbe essere affrontato in modo molto più affidabile utilizzando lo spazio dei nomi dei protocolli di livello inferiore. Quello che ho notato è che quando il certificato autofirmato viene correttamente aggiunto all'archivio certificati macchina locale posso impostare il callback VerifyServerCertificate per validare il certificato con esito positivo, qualcosa come "delegato (connessione LdapConnection, certificato X509Certificate) {return new X509Certificate2 (certificato) .Verify()} ", ma non ottengo ancora alcuna gioia con DirectoryEntry. – Andrew

+0

Ho anche notato che durante gli eventi di sistema durante i tentativi di DirectoryEntry falliti ottengo quanto segue: "Il certificato ricevuto dal server remoto non contiene il nome previsto, quindi non è possibile determinare se ci stiamo connettendo al server corretto. Il nome del server che ci aspettavamo è MYSERVER.MYDOMAIN.CO.UK. La richiesta di connessione SSL non è riuscita. I dati allegati contengono il certificato del server. " Lo sto esaminando ora, ma penso che la risposta sia ancora lo spazio dei nomi dei protocolli per la massima fiducia e una minore dipendenza ambientale. – Andrew

+0

Andrew, grazie per il commento. Sembra che stiamo lavorando per un obiettivo molto simile. L'errore che vedi negli eventi di sistema coincide con molte delle informazioni che ho letto sulla rete riguardo a chi viene rilasciato il certificato. Molti dei server che eseguono SSL con cui sto provando a interagire hanno un certificato che viene rilasciato su un dominio diverso da quello su cui sto legando in LdapConnection ... e questo certamente causa il fallimento di DirectoryEntry. – X3074861X

2

Prometto, questo sarà il mio ultimo post su questa particolare domanda. :)

Dopo un'altra settimana di ricerca e sviluppo, ho una buona soluzione a questo, e ha funzionato molto bene per me finora.

L'approccio è leggermente diverso dalla mia prima risposta, ma in generale è lo stesso concetto; utilizzando LdapConnection per forzare la convalida del certificato.

//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]; 

L'altra cosa che volevo da notare qui è che SearchResultEntry tornerà utente "attributi" invece di "proprietà".

Gli attributi vengono restituiti come matrici di byte, quindi è necessario codificarli per ottenere la rappresentazione di stringa. Fortunatamente System.Text.Encoding contiene una classe ASCIIEncoding nativa in grado di gestirli molto facilmente.

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

E questo è tutto! Molto felice di aver finalmente capito.

Cheers!

+0

Grazie anche a questo, ho anche deciso di riprogettare per utilizzare l'assembly System.DirectoryServices.Protocols alla fine. Certo, è un po 'procedurale, ma avendo lavorato con le librerie LDAP Win32 prima si sente molto più vicino al metallo. E mi sento più sicuro che il codice sarà adattabile ai requisiti futuri rispetto al codice che utilizza System.DirectoryServices basato su ADSI. – Andrew

0

Ho usato il codice seguente per connettersi con ldaps utilizzando DirectoryEntry.

quello che ho capito nella mia scenerio è DirectoryEntry non funziona quando ldaps è specificato nel percorso del server o tipo di autenticazione viene menzionato come "AuthenticationTypes.SecureSocketsLayer", ma se ldaps unico porto è menzionato alla fine del nome del server è il lavoro . Dopo aver dato un'occhiata al log di wireshark posso vedere la stretta di mano che si svolge come menzionato nel post precedente.

Stretta di mano: enter image description here

Codice:

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; 
     } 
    } 
Problemi correlati