Установить обратный вызов для System.DirectoryServices.DirectoryEntry для обработки самоподписанного сертификата SSL?

У меня есть приложение, реплицирующее данные из службы каталогов с использованием типичного кода System.DirectoryServices.DirectoryEntry. Теперь у меня есть требование выполнить репликацию из Novell eDirectory с использованием SSL с самозаверяющим сертификатом. Я подозреваю, что существующий код будет работать с действующим сертификатом, который можно проверить, или, возможно, если самозаверяющий сертификат будет добавлен в хранилище ключей локального компьютера. Однако для того, чтобы он работал с самоподписанным сертификатом, единственное решение, которое я могу найти, - это использовать пространство имен System.DirectoryServices.Protocols и класс LdapConnection, посредством чего я могу подключить обратный вызов VerifyServerCertificate. Я не могу найти способ применить ту же концепцию к экземпляру DirectoryEntry или подключиться к экземпляру LdapConnection и каким-то образом «преобразовать» его в экземпляр DirectoryEntry. Может быть, это невозможно, я просто хочу это подтвердить. Любые другие мысли приветствуются.

Единственная подходящая ссылка, которую я нашел, находится по адресу: http://www.codeproject.com/Articles/19097/eDirectory-Authentication-using-LdapConnection-and


person Andrew    schedule 16.10.2012    source источник


Ответы (3)


Это феноменальный вопрос.

Я боролся с этой же проблемой уже несколько дней, и я, наконец, получил окончательное доказательство того, почему объект DirectoryEntry не будет работать в этом сценарии.

Этот конкретный сервер Ldap (работающий на LDAPS 636) также выдает собственный самоподписанный сертификат. Используя LdapConnection (и отслеживая трафик через Wireshark), я заметил, что происходит рукопожатие, которого не происходит при использовании DirectoryEntry:

введите описание изображения здесь

Первая последовательность - с защищенного сервера ldap, вторая - с моей машины. Код, который предлагает вторую последовательность:

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

Есть и другие способы «подделать» обратный вызов, но это то, что я использовал.

К сожалению, DirectoryEntry не имеет опции или метода для проверки самоподписанного сертификата, поэтому принятие сертификата никогда не происходит (вторая последовательность), и соединение не инициализируется.

Единственный возможный способ добиться этого - использовать LdapConnection в сочетании с SearchRequest и SearchResponse. Вот что у меня есть на данный момент:

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

Оттуда вы можете получить свойства AD из SearchResponse и обработать их соответствующим образом. Это полный облом, потому что SearchRequest кажется намного медленнее, чем использование DirectoryEntry.

Надеюсь это поможет!

person X3074861X    schedule 25.10.2012
comment
Привет. Спасибо за вклад, очень признателен. Я также прихожу к выводу, что вся проблема может быть решена гораздо более надежно, используя пространство имен более низкого уровня Protocols. Я заметил, что когда самозаверяющий сертификат правильно добавлен в хранилище сертификатов локального компьютера, я могу установить обратный вызов VerifyServerCertificate для успешной проверки сертификата, что-то вроде делегата (соединение LdapConnection, сертификат X509Certificate) {return new X509Certificate2 (certificate ) .Verify ()}, но я все еще не получаю удовольствия от DirectoryEntry. - person Andrew; 26.10.2012
comment
Я также заметил, что в моих системных событиях во время неудачных попыток DirectoryEntry я получаю следующее: Сертификат, полученный с удаленного сервера, не содержит ожидаемого имени. Поэтому невозможно определить, подключаемся ли мы к правильному серверу. Ожидаемое имя сервера - MYSERVER.MYDOMAIN.CO.UK. Запрос на соединение SSL не удался. Прилагаемые данные содержат сертификат сервера. Я сейчас занимаюсь этим, но думаю, что ответом по-прежнему остается пространство имен Protocols для максимальной уверенности и уменьшения зависимости от окружающей среды. - person Andrew; 26.10.2012
comment
Андрей, спасибо за комментарий. Похоже, мы работаем над очень похожей целью. Эта ошибка, которую вы видите в системных событиях, совпадает с большой частью информации, которую я прочитал в сети, относительно того, кому выдан сертификат. Многие из серверов, использующих SSL, с которыми я пытаюсь взаимодействовать, имеют сертификат, который выдается другому домену, чем тот, который я привязываю в LdapConnection ... и это, безусловно, приводит к сбою DirectoryEntry. - person X3074861X; 26.10.2012

Обещаю, это будет мой последний пост по этому вопросу. :)

После еще одной недели исследований и разработок у меня есть хорошее решение этой проблемы, и до сих пор оно работало для меня очень хорошо.

Подход несколько отличается от моего первого ответа, но в целом это та же концепция; используя LdapConnection для принудительной проверки сертификата.

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

Еще я хотел здесь отметить, что SearchResultEntry будет возвращать пользовательские «атрибуты» вместо «свойств».

Атрибуты возвращаются в виде байтовых массивов, поэтому вам необходимо закодировать их, чтобы получить строковое представление. К счастью, System.Text.Encoding содержит собственный класс ASCIIEncoding, который очень легко справляется с этим.

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

Вот и все! Очень рад, что наконец-то это выяснилось.

Ваше здоровье!

person X3074861X    schedule 09.11.2012
comment
Спасибо за это, я также решил перепроектировать, чтобы в конце использовать сборку System.DirectoryServices.Protocols. Конечно, это немного процедурно, но, поработав с библиотеками Win32 LDAP, он почувствовал себя намного ближе к металлу. И я уверен, что код будет адаптирован к будущим требованиям, чем код, использующий System.DirectoryServices на основе ADSI. - person Andrew; 12.11.2012
comment
Кто-нибудь создал древовидную структуру, используя SearchResultEntry, упомянутый выше? в C # - person CodeConstruct; 30.07.2019

Я использовал приведенный ниже код для подключения к ldaps с помощью DirectoryEntry.

В моем сценарии я понял, что directoryEntry не работает, если в пути к серверу указан ldaps или тип аутентификации указан как «AuthenticationTypes.SecureSocketsLayer», но если в конце имени сервера указан только порт ldaps, он Работа. После просмотра журнала wirehark я вижу, как происходит рукопожатие, как указано в сообщении выше.

Рукопожатие:  введите описание изображения здесь

Код:

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;
        }
    }
person MVijayvargia    schedule 03.11.2015