【问题标题】:Set callback for System.DirectoryServices.DirectoryEntry to handle self-signed SSL certificate?为 System.DirectoryServices.DirectoryEntry 设置回调以处理自签名 SSL 证书?
【发布时间】:2012-10-06 08:35:02
【问题描述】:

我有一个应用程序使用典型的 System.DirectoryServices.DirectoryEntry 代码从目录服务复制数据。我现在需要使用带有自签名证书的 SSL 从 Novell eDirectory 进行复制。我怀疑现有代码可以使用可以验证的有效证书,或者如果将自签名证书添加到本地计算机密钥库中。然而,为了确保它与自签名证书一起工作,我能找到的唯一解决方案是使用 System.DirectoryServices.Protocols 命名空间和 LdapConnection 类,从而我可以连接一个 VerifyServerCertificate 回调。我找不到任何方法将相同的概念应用于 DirectoryEntry 实例,或连接 LdapConnection 实例并以某种方式将其“转换”为 DirectoryEntry 实例。也许不可能,我只是想确认一下。欢迎任何其他想法。

我找到的唯一相关链接是:http://www.codeproject.com/Articles/19097/eDirectory-Authentication-using-LdapConnection-and

【问题讨论】:

    标签: ssl active-directory ldap directoryservices edirectory


    【解决方案1】:

    这是一个了不起的问题。

    我这几天一直在与同样的问题作斗争,我终于得到了一些明确的证据来说明为什么 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;}
    

    从那里您可以从 SearchResponse 中收集 AD 属性,并进行相应的处理。不过这实在是太糟糕了,因为 SearchRequest 似乎比使用 DirectoryEntry 慢得多。

    希望这会有所帮助!

    【讨论】:

    • 嗨。感谢您的输入,非常感谢。我还得出结论,使用较低级别的 Protocols 命名空间可以更可靠地解决整个问题。我注意到的是,当自签名证书正确添加到本地计算机证书存储时,我可以设置 VerifyServerCertificate 回调以成功验证证书,例如“委托(LdapConnection 连接,X509Certificate 证书){ return new X509Certificate2( certificate).Verify() }",但我仍然对 DirectoryEntry 不满意。
    • 我还注意到,在 DirectoryEntry 尝试失败期间,我在系统事件中收到以下信息:“从远程服务器收到的证书不包含预期的名称。因此无法确定我们是否正在连接到正确的服务器。我们期望的服务器名称是 MYSERVER.MYDOMAIN.CO.UK。SSL 连接请求失败。附加数据包含服务器证书。我现在正在研究这个问题,但我认为答案仍然是 Protocols 命名空间,以最大程度地提高信心并减少对环境的依赖。
    • 安德鲁,感谢您的评论。看来我们正在朝着一个非常相似的目标努力。您在系统事件中看到的错误与我在网上阅读的有关证书颁发给谁的大量信息相吻合。我尝试与之交互的许多运行 SSL 的服务器都有一个证书,该证书颁发给与我在 LdapConnection 中绑定的域不同的域......这肯定会导致 DirectoryEntry 失败。跨度>
    【解决方案2】:

    我保证,这将是我关于这个特定问题的最后一篇文章。 :)

    经过一周的研究和开发,我有一个很好的解决方案,到目前为止它对我来说非常有效。

    方法与我的第一个答案有些不同,但总的来说,它是相同的概念;使用 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);
    

    就是这样!很高兴终于解决了这个问题。

    干杯!

    【讨论】:

    • 感谢您,我还决定重新设计最终使用 System.DirectoryServices.Protocols 程序集。当然,这有点程序化,但在感觉更接近金属之前曾使用过 Win32 LDAP 库。而且我更有信心,该代码将比使用基于 ADSI 的 System.DirectoryServices 的代码适应未来的需求。
    • 有没有人使用上面提到的 SearchResultEntry 制作了一个树形结构?在 C# 中
    【解决方案3】:

    我使用下面的代码通过 DirectoryEntry 与 ldaps 连接。

    我在我的场景中理解的是,当在服务器路径中指定 ldaps 或身份验证类型被称为“AuthenticationTypes.SecureSocketsLayer”时,directoryEntry 不起作用,但如果在末尾只提到 ldaps 端口服务器名称它工作。查看wireshark日志后,我可以看到上面提到的握手发生了。

    握手:

    代码:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-28
      • 2011-02-06
      • 2012-11-01
      • 1970-01-01
      • 2014-03-24
      • 1970-01-01
      • 2015-03-11
      • 2020-10-25
      相关资源
      最近更新 更多