Skip to content

Commit

Permalink
[release/9.0-staging] Add support for LDAPTLS_CACERTDIR \ TrustedCert…
Browse files Browse the repository at this point in the history
…ificateDirectory (#112531)

* Add support for LDAPTLS_CACERTDIR \ TrustedCertificateDirectory (#111877)

* Add CompatibilitySuppressions.xml

* Remove unwanted test changes that were ported from v10
  • Loading branch information
steveharter authored Feb 26, 2025
1 parent 109f957 commit a327ddd
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 26 deletions.
2 changes: 2 additions & 0 deletions src/libraries/Common/src/Interop/Interop.Ldap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ internal enum LdapOption
LDAP_OPT_ROOTDSE_CACHE = 0x9a, // Not Supported in Linux
LDAP_OPT_DEBUG_LEVEL = 0x5001,
LDAP_OPT_URI = 0x5006, // Not Supported in Windows
LDAP_OPT_X_TLS_CACERTDIR = 0x6003, // Not Supported in Windows
LDAP_OPT_X_TLS_NEWCTX = 0x600F, // Not Supported in Windows
LDAP_OPT_X_SASL_REALM = 0x6101,
LDAP_OPT_X_SASL_AUTHCID = 0x6102,
LDAP_OPT_X_SASL_AUTHZID = 0x6103
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Configuration>
<CommentThatAllowsDoubleHyphens>
To enable the tests marked with [ConditionalFact(nameof(IsLdapConfigurationExist))], you need to setup an LDAP server and provide the needed server info here.
To enable the tests marked with [ConditionalFact(nameof(IsLdapConfigurationExist))], you need to setup an LDAP server as described below and set the environment variable LDAP_TEST_SERVER_INDEX to the appropriate offset into the XML section found at the end of this file.

To ship, we should test on both an Active Directory LDAP server, and at least one other server, as behaviors are a little different. However for local testing, it is easiest to connect to an OpenDJ LDAP server in a docker container (eg., in WSL2).

Expand All @@ -11,7 +11,7 @@ OPENDJ SERVER

test it with this command - it should return some results in WSL2

ldapsearch -h localhost -p 1389 -D 'cn=admin,dc=example,dc=com' -x -w password
ldapsearch -H ldap://localhost:1389 -D 'cn=admin,dc=example,dc=com' -x -w password

this command views the status

Expand All @@ -24,16 +24,16 @@ SLAPD OPENLDAP SERVER

and to test and view status

ldapsearch -h localhost -p 390 -D 'cn=admin,dc=example,dc=com' -x -w password
ldapsearch -H ldap://localhost:390 -D 'cn=admin,dc=example,dc=com' -x -w password

docker exec -it slapd01 slapcat

SLAPD OPENLDAP SERVER WITH TLS
==============================

The osixia/openldap container image automatically creates a TLS lisener with a self-signed certificate. This can be used to test TLS.
The osixia/openldap container image automatically creates a TLS listener with a self-signed certificate. This can be used to test TLS.

Start the container, with TLS on port 1636, without client certificate verification:
Start the container, with TLS on port 1636, but without client certificate verification:

docker run --publish 1389:389 --publish 1636:636 --name ldap --hostname ldap.local --detach --rm --env LDAP_TLS_VERIFY_CLIENT=never --env LDAP_ADMIN_PASSWORD=password osixia/openldap --loglevel debug

Expand All @@ -56,6 +56,8 @@ To test and view the status:

ldapsearch -H ldaps://ldap.local:1636 -b dc=example,dc=org -x -D cn=admin,dc=example,dc=org -w password

use '-d 1' or '-d 2' for debugging.

ACTIVE DIRECTORY
================

Expand All @@ -65,7 +67,7 @@ When running against Active Directory from a Windows client, you should not see

If you are running your AD server as a VM on the same machine that you are running WSL2, you must execute this command on the host to bridge the two Hyper-V networks so that it is visible from WSL2:

Get-NetIPInterface | where {$_.InterfaceAlias -eq 'vEthernet (WSL)' -or $_.InterfaceAlias -eq 'vEthernet (Default Switch)'} | Set-NetIPInterface -Forwarding Enabled
Get-NetIPInterface | where {$_.InterfaceAlias -eq 'vEthernet (WSL)' -or $_.InterfaceAlias -eq 'vEthernet (Default Switch)'} | Set-NetIPInterface -Forwarding Enabled

The WSL2 VM should now be able to see the AD VM by IP address. To make it visible by host name, it's probably easiest to just add it to /etc/hosts.

Expand All @@ -82,7 +84,7 @@ Note:
</CommentThatAllowsDoubleHyphens>

<!-- To choose a connection, set an environment variable LDAP_TEST_SERVER_INDEX
to the zero-based index, eg., 0, 1, or 2
to the zero-based index, eg., 0, 1, 2, or 3.
If you don't set LDAP_TEST_SERVER_INDEX then tests that require a server
will skip.
-->
Expand All @@ -105,15 +107,6 @@ Note:
<AuthenticationTypes>ServerBind,None</AuthenticationTypes>
<SupportsServerSideSort>False</SupportsServerSideSort>
</Connection>
<Connection Name="ACTIVE DIRECTORY SERVER">
<ServerName>danmose-ldap.danmose-domain.com</ServerName>
<SearchDN>DC=danmose-domain,DC=com</SearchDN>
<Port>389</Port>
<User>danmose-domain\Administrator</User>
<Password>%TESTPASSWORD%</Password>
<AuthenticationTypes>ServerBind,None</AuthenticationTypes>
<SupportsServerSideSort>True</SupportsServerSideSort>
</Connection>
<Connection Name="SLAPD OPENLDAP SERVER TLS">
<ServerName>ldap.local</ServerName>
<SearchDN>DC=example,DC=org</SearchDN>
Expand All @@ -124,5 +117,14 @@ Note:
<UseTls>true</UseTls>
<SupportsServerSideSort>False</SupportsServerSideSort>
</Connection>
<Connection Name="ACTIVE DIRECTORY SERVER">
<ServerName>danmose-ldap.danmose-domain.com</ServerName>
<SearchDN>DC=danmose-domain,DC=com</SearchDN>
<Port>389</Port>
<User>danmose-domain\Administrator</User>
<Password>%TESTPASSWORD%</Password>
<AuthenticationTypes>ServerBind,None</AuthenticationTypes>
<SupportsServerSideSort>True</SupportsServerSideSort>
</Connection>

</Configuration>
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,8 @@ public partial class LdapSessionOptions
internal LdapSessionOptions() { }
public bool AutoReconnect { get { throw null; } set { } }
public string DomainName { get { throw null; } set { } }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")]
public string TrustedCertificatesDirectory { get { throw null; } set { } }
public string HostName { get { throw null; } set { } }
public bool HostReachable { get { throw null; } }
public System.DirectoryServices.Protocols.LocatorFlags LocatorFlag { get { throw null; } set { } }
Expand All @@ -402,6 +404,8 @@ internal LdapSessionOptions() { }
public bool Signing { get { throw null; } set { } }
public System.DirectoryServices.Protocols.SecurityPackageContextConnectionInformation SslInformation { get { throw null; } }
public int SspiFlag { get { throw null; } set { } }
[System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")]
public void StartNewTlsSessionContext() { }
public bool TcpKeepAlive { get { throw null; } set { } }
public System.DirectoryServices.Protocols.VerifyServerCertificateCallback VerifyServerCertificate { get { throw null; } set { } }
public void FastConcurrentBind() { }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://learn.microsoft.com/dotnet/fundamentals/package-validation/diagnostic-ids -->
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:System.DirectoryServices.Protocols.LdapSessionOptions.get_TrustedCertificatesDirectory</Target>
<Left>lib/net8.0/System.DirectoryServices.Protocols.dll</Left>
<Right>lib/net8.0/System.DirectoryServices.Protocols.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:System.DirectoryServices.Protocols.LdapSessionOptions.set_TrustedCertificatesDirectory(System.String)</Target>
<Left>lib/net8.0/System.DirectoryServices.Protocols.dll</Left>
<Right>lib/net8.0/System.DirectoryServices.Protocols.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:System.DirectoryServices.Protocols.LdapSessionOptions.StartNewTlsSessionContext</Target>
<Left>lib/net8.0/System.DirectoryServices.Protocols.dll</Left>
<Right>lib/net8.0/System.DirectoryServices.Protocols.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:System.DirectoryServices.Protocols.LdapSessionOptions.get_TrustedCertificatesDirectory</Target>
<Left>lib/net9.0/System.DirectoryServices.Protocols.dll</Left>
<Right>lib/net9.0/System.DirectoryServices.Protocols.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:System.DirectoryServices.Protocols.LdapSessionOptions.set_TrustedCertificatesDirectory(System.String)</Target>
<Left>lib/net9.0/System.DirectoryServices.Protocols.dll</Left>
<Right>lib/net9.0/System.DirectoryServices.Protocols.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:System.DirectoryServices.Protocols.LdapSessionOptions.StartNewTlsSessionContext</Target>
<Left>lib/net9.0/System.DirectoryServices.Protocols.dll</Left>
<Right>lib/net9.0/System.DirectoryServices.Protocols.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:System.DirectoryServices.Protocols.LdapSessionOptions.get_TrustedCertificatesDirectory</Target>
<Left>lib/netstandard2.0/System.DirectoryServices.Protocols.dll</Left>
<Right>lib/netstandard2.0/System.DirectoryServices.Protocols.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:System.DirectoryServices.Protocols.LdapSessionOptions.set_TrustedCertificatesDirectory(System.String)</Target>
<Left>lib/netstandard2.0/System.DirectoryServices.Protocols.dll</Left>
<Right>lib/netstandard2.0/System.DirectoryServices.Protocols.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:System.DirectoryServices.Protocols.LdapSessionOptions.StartNewTlsSessionContext</Target>
<Left>lib/netstandard2.0/System.DirectoryServices.Protocols.dll</Left>
<Right>lib/netstandard2.0/System.DirectoryServices.Protocols.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
</Suppressions>
Original file line number Diff line number Diff line change
Expand Up @@ -426,4 +426,7 @@
<data name="ReferralChasingOptionsNotSupported" xml:space="preserve">
<value>Only ReferralChasingOptions.None and ReferralChasingOptions.All are supported on Linux.</value>
</data>
<data name="DirectoryNotFound" xml:space="preserve">
<value>The directory '{0}' does not exist.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -955,13 +955,13 @@ private unsafe Interop.BOOL ProcessClientCertificate(IntPtr ldapHandle, IntPtr C

private void Connect()
{
//Ccurrently ldap does not accept more than one certificate.
// Currently ldap does not accept more than one certificate.
if (ClientCertificates.Count > 1)
{
throw new InvalidOperationException(SR.InvalidClientCertificates);
}

// Set the certificate callback routine here if user adds the certifcate to the certificate collection.
// Set the certificate callback routine here if user adds the certificate to the certificate collection.
if (ClientCertificates.Count != 0)
{
int certError = LdapPal.SetClientCertOption(_ldapHandle, LdapOption.LDAP_OPT_CLIENT_CERTIFICATE, _clientCertificateRoutine);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;
using System.IO;
using System.Runtime.Versioning;

namespace System.DirectoryServices.Protocols
{
Expand All @@ -11,6 +13,34 @@ public partial class LdapSessionOptions

private bool _secureSocketLayer;

/// <summary>
/// Specifies the path of the directory containing CA certificates in the PEM format.
/// Multiple directories may be specified by separating with a semi-colon.
/// </summary>
/// <remarks>
/// The certificate files are looked up by the CA subject name hash value where that hash can be
/// obtained by using, for example, <code>openssl x509 -hash -noout -in CA.crt</code>.
/// It is a common practice to have the certificate file be a symbolic link to the actual certificate file
/// which can be done by using <code>openssl rehash .</code> or <code>c_rehash .</code> in the directory
/// containing the certificate files.
/// </remarks>
/// <exception cref="DirectoryNotFoundException">The directory not exist.</exception>
[UnsupportedOSPlatform("windows")]
public string TrustedCertificatesDirectory
{
get => GetStringValueHelper(LdapOption.LDAP_OPT_X_TLS_CACERTDIR, releasePtr: true);

set
{
if (!Directory.Exists(value))
{
throw new DirectoryNotFoundException(SR.Format(SR.DirectoryNotFound, value));
}

SetStringOptionHelper(LdapOption.LDAP_OPT_X_TLS_CACERTDIR, value);
}
}

public bool SecureSocketLayer
{
get
Expand Down Expand Up @@ -52,6 +82,16 @@ public ReferralChasingOptions ReferralChasing
}
}

/// <summary>
/// Create a new TLS library context.
/// Calling this is necessary after setting TLS-based options, such as <c>TrustedCertificatesDirectory</c>.
/// </summary>
[UnsupportedOSPlatform("windows")]
public void StartNewTlsSessionContext()
{
SetIntValueHelper(LdapOption.LDAP_OPT_X_TLS_NEWCTX, 0);
}

private bool GetBoolValueHelper(LdapOption option)
{
if (_connection._disposed) throw new ObjectDisposedException(GetType().Name);
Expand All @@ -71,5 +111,14 @@ private void SetBoolValueHelper(LdapOption option, bool value)

ErrorChecking.CheckAndSetLdapError(error);
}

private void SetStringOptionHelper(LdapOption option, string value)
{
if (_connection._disposed) throw new ObjectDisposedException(GetType().Name);

int error = LdapPal.SetStringOption(_connection._ldapHandle, option, value);

ErrorChecking.CheckAndSetLdapError(error);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ public partial class LdapSessionOptions
{
private static void PALCertFreeCRLContext(IntPtr certPtr) => Interop.Ldap.CertFreeCRLContext(certPtr);

[UnsupportedOSPlatform("windows")]
public string TrustedCertificatesDirectory
{
get => throw new PlatformNotSupportedException();
set => throw new PlatformNotSupportedException();
}

public bool SecureSocketLayer
{
get
Expand All @@ -24,6 +31,9 @@ public bool SecureSocketLayer
}
}

[UnsupportedOSPlatform("windows")]
public void StartNewTlsSessionContext() => throw new PlatformNotSupportedException();

public int ProtocolVersion
{
get => GetIntValueHelper(LdapOption.LDAP_OPT_VERSION);
Expand Down
Loading

0 comments on commit a327ddd

Please sign in to comment.