Skip to content

LDAP StartTLS not working on .NET 6 Preview 6 on Linux #56619

@iinuwa

Description

@iinuwa

Description

Using LdapSessionOptions.StartTransportLayerSecurity() fails on .NET 6 Preview 6 on Linux.

using System;
using System.Net;
using System.Text;
using System.IO;
using System.DirectoryServices.Protocols;

var identifier = new LdapDirectoryIdentifier("localhost", 389);
var cred = new NetworkCredential("cn=admin,dc=example,dc=org", "password");
using var conn = new LdapConnection(identifier, cred);
conn.SessionOptions.ProtocolVersion = 3;
conn.SessionOptions.StartTransportLayerSecurity(null); // Exception thrown here
conn.Bind();

fails with

Unhandled exception. System.DirectoryServices.Protocols.LdapException: The LDAP server is unavailable.
   at System.DirectoryServices.Protocols.LdapSessionOptions.StartTransportLayerSecurity(DirectoryControlCollection controls) in System.DirectoryServices.Protocols.dll:token 0x60002c1+0x361
   at <Program>$.<Main>$(String[] args) in /src/ldapStartTlsTest/Program.cs:line 15

Configuration

.NET 6 Preview 6, Linux x86-64

Regression?

This worked in .NET 6 Preview 5.

Other information

When stepping through the problem it fails on the interop call to ldap_start_tls_s in LdapSessionOptions.cs:608

I think there are two problems: First, LdapPal.StartTls and Interop.Ldap.ldap_start_tls have the wrong method signatures. I think that's an easy fix.

The second is more complex. OpenLDAP's ldap_start_tls needs to be called after the URI is configured for the LDAP handle. But in main, the URI isn't configured before SessionOptions.StartTransportLayerSecurity() is called by clients. This is because OpenLDAP requires the scheme to be "fully initialized." The parameters needed to determine the URI scheme are LdapDirectoryIdentifier.Connectionless, Servers and PortNumber, and SessionOptions.SecureSocketLayer. The SSL option is the problematic one, since it can't be set until after the LdapConnection is finished being constructed.

Here are a couple of options:

  • When SessionOptions.StartTransportLayerSecurity() is called, store a _startTls bool on the LdapConnection and defer calling ldap_start_tls until the Connect() phase.
  • During Init(), Initialize the LDAP handle with the ldap:// scheme (or cldap:// since LdapDirectoryIdentifier.Connectionless is known at that time). Then during Connect(), check if SecureSocketsLayer is set, and then rebuild the URI.
    • This means we'd have to build the URI list twice if SecureSocketsLayer is set.
    • If there are no other pre-bind operations that can take place using S.DS.P, then this might be an OK option.

I'm working on a PR that implements the first option, but I don't know how to write an automated test for it. I've tested manually, and viewed the output in Wireshark to confirm that it works. We can't just pass if it binds successfully with that option set, since the credentials would still fall through to the server. Maybe there's a way to restrict an LDAP server only to use StartTLS and not plain text, or to attempt to bind to a random TCP socket and detect whether the StartTLS operation was sent to it?

Metadata

Metadata

Assignees

Type

No type

Projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions