From 510c98b76d177608732562d70b73e4257bb7c582 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Mon, 27 Jan 2025 11:01:37 -0600 Subject: [PATCH 1/6] Add support for setting CertificateDirectory --- .../Common/src/Interop/Interop.Ldap.cs | 2 ++ .../DirectoryServices/LDAP.Configuration.xml | 4 +-- .../ref/System.DirectoryServices.Protocols.cs | 4 +++ .../Protocols/ldap/LdapConnection.cs | 4 +-- .../ldap/LdapSessionOptions.Linux.cs | 28 ++++++++++++++++ .../ldap/LdapSessionOptions.Windows.cs | 10 ++++++ .../tests/DirectoryServicesProtocolsTests.cs | 33 ++++++++++++++++--- .../tests/DirectoryServicesTestHelpers.cs | 10 ++++-- .../tests/LdapSessionOptionsTests.cs | 27 +++++++++++++++ 9 files changed, 110 insertions(+), 12 deletions(-) diff --git a/src/libraries/Common/src/Interop/Interop.Ldap.cs b/src/libraries/Common/src/Interop/Interop.Ldap.cs index 90c0ba997cd962..512242230093ff 100644 --- a/src/libraries/Common/src/Interop/Interop.Ldap.cs +++ b/src/libraries/Common/src/Interop/Interop.Ldap.cs @@ -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 diff --git a/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml b/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml index 11e42e4fcf4ff6..500a9ccb4e7e04 100644 --- a/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml +++ b/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml @@ -6,11 +6,11 @@ To ship, we should test on both an Active Directory LDAP server, and at least on When testing with later of versions of LDAP, the ldapsearch commands below may need to use - -H ldap://localhost: + -H ldap://localhost:YOURPORT instead of - -h localhost -p + -h localhost -p YOURPORT OPENDJ SERVER ============= diff --git a/src/libraries/System.DirectoryServices.Protocols/ref/System.DirectoryServices.Protocols.cs b/src/libraries/System.DirectoryServices.Protocols/ref/System.DirectoryServices.Protocols.cs index cf1b9154d7befe..579c5ce047fca7 100644 --- a/src/libraries/System.DirectoryServices.Protocols/ref/System.DirectoryServices.Protocols.cs +++ b/src/libraries/System.DirectoryServices.Protocols/ref/System.DirectoryServices.Protocols.cs @@ -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 CertificateDirectory { 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 { } } @@ -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() { } diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.cs index 1125bfd568d385..facdfc6a484bb9 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapConnection.cs @@ -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); diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs index e1cfffebb531fc..14cb8df47e7171 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs @@ -11,6 +11,16 @@ public partial class LdapSessionOptions private bool _secureSocketLayer; + /// + /// Specifies the path of the directory containing CA certificates. + /// + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] + public string CertificateDirectory + { + get => GetStringValueHelper(LdapOption.LDAP_OPT_X_TLS_CACERTDIR, releasePtr: true); + set => SetStringOptionHelper(LdapOption.LDAP_OPT_X_TLS_CACERTDIR, value); + } + public bool SecureSocketLayer { get @@ -52,6 +62,15 @@ public ReferralChasingOptions ReferralChasing } } + /// + /// Create a new TLS library context. + /// + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("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); @@ -71,5 +90,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); + } } } diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs index 813005c5ecb72b..263780ada2e674 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs @@ -10,6 +10,13 @@ public partial class LdapSessionOptions { private static void PALCertFreeCRLContext(IntPtr certPtr) => Interop.Ldap.CertFreeCRLContext(certPtr); + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] + public string CertificateDirectory + { + get => throw new PlatformNotSupportedException(); + set => throw new PlatformNotSupportedException(); + } + public bool SecureSocketLayer { get @@ -24,6 +31,9 @@ public bool SecureSocketLayer } } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] + public void StartNewTlsSessionContext() => throw new PlatformNotSupportedException(); + public int ProtocolVersion { get => GetIntValueHelper(LdapOption.LDAP_OPT_VERSION); diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs index 64c8487c7b481e..3b44e72354658f 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs +++ b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs @@ -2,12 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Diagnostics; using System.DirectoryServices.Tests; using System.Globalization; using System.Net; -using System.Text; -using System.Threading; using Xunit; namespace System.DirectoryServices.Protocols.Tests @@ -706,6 +703,32 @@ public void TestMultipleServerBind() connection.Timeout = new TimeSpan(0, 3, 0); } +#if NET + [ConditionalFact(nameof(LdapConfigurationExists))] + [PlatformSpecific(TestPlatforms.Linux)] + public void StartNewTlsSessionContext_ThrowsLdapException() + { + using (var connection = new LdapConnection("server")) + { + LdapSessionOptions options = connection.SessionOptions; + + // To get this to not throw, we need to set CertificateDirectory and have a .crt file in that directory. + Assert.Throws(() => options.StartNewTlsSessionContext()); + } + } + + [ConditionalFact(nameof(LdapConfigurationExists))] + [PlatformSpecific(TestPlatforms.Windows)] + public void StartNewTlsSessionContext_ThrowsPlatformNotSupportedException() + { + using (var connection = new LdapConnection("server")) + { + LdapSessionOptions options = connection.SessionOptions; + Assert.Throws(() => options.StartNewTlsSessionContext()); + } + } +#endif + private void DeleteAttribute(LdapConnection connection, string entryDn, string attributeName) { string dn = entryDn + "," + LdapConfiguration.Configuration.SearchDn; @@ -786,14 +809,14 @@ private SearchResultEntry SearchUser(LdapConnection connection, string rootDn, s return null; } - private LdapConnection GetConnection(string server) + private static LdapConnection GetConnection(string server) { LdapDirectoryIdentifier directoryIdentifier = new LdapDirectoryIdentifier(server, fullyQualifiedDnsHostName: true, connectionless: false); return GetConnection(directoryIdentifier); } - private LdapConnection GetConnection() + private static LdapConnection GetConnection() { LdapDirectoryIdentifier directoryIdentifier = string.IsNullOrEmpty(LdapConfiguration.Configuration.Port) ? new LdapDirectoryIdentifier(LdapConfiguration.Configuration.ServerName, fullyQualifiedDnsHostName: true, connectionless: false) : diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesTestHelpers.cs b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesTestHelpers.cs index 422c37e70dbb30..6f318fd803e416 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesTestHelpers.cs +++ b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesTestHelpers.cs @@ -31,14 +31,18 @@ public static bool IsLibLdapInstalled } else { - _isLibLdapInstalled = NativeLibrary.TryLoad("libldap-2.4.so.2", out _); + _isLibLdapInstalled = + NativeLibrary.TryLoad("libldap.so.2", out _) || + NativeLibrary.TryLoad("libldap-2.6.so.0", out _) || + NativeLibrary.TryLoad("libldap-2.5.so.0", out _) || + NativeLibrary.TryLoad("libldap-2.4.so.2", out _); } } - return _isLibLdapInstalled.Value; #else _isLibLdapInstalled = true; // In .NET Framework ldap is always installed. - return _isLibLdapInstalled.Value; #endif + + return _isLibLdapInstalled.Value; } } } diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/LdapSessionOptionsTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/LdapSessionOptionsTests.cs index 5f6a737834ac23..1967ffda17dc82 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/LdapSessionOptionsTests.cs +++ b/src/libraries/System.DirectoryServices.Protocols/tests/LdapSessionOptionsTests.cs @@ -7,6 +7,8 @@ namespace System.DirectoryServices.Protocols.Tests { + // To enable these tests locally for Mono, comment out this line in DirectoryServicesTestHelpers.cs: + // [assembly: ActiveIssue("https://github.com/dotnet/runtime/issues/35912", TestRuntimes.Mono)] [ConditionalClass(typeof(DirectoryServicesTestHelpers), nameof(DirectoryServicesTestHelpers.IsWindowsOrLibLdapIsInstalled))] public class LdapSessionOptionsTests { @@ -756,5 +758,30 @@ public void StopTransportLayerSecurity_Disposed_ThrowsObjectDisposedException() Assert.Throws(() => connection.SessionOptions.StopTransportLayerSecurity()); } + +#if NET + [Fact] + [PlatformSpecific(TestPlatforms.Linux)] + public void CertificateDirectoryProperty() + { + using (var connection = new LdapConnection("server")) + { + LdapSessionOptions options = connection.SessionOptions; + options.CertificateDirectory = "CertificateDirectory"; + Assert.Equal("CertificateDirectory", options.CertificateDirectory); + } + } + + [Fact] + [PlatformSpecific(TestPlatforms.Windows)] + public void CertificateDirectoryProperty_ThrowsPlatformNotSupportedException() + { + using (var connection = new LdapConnection("server")) + { + LdapSessionOptions options = connection.SessionOptions; + Assert.Throws(() => options.CertificateDirectory = "CertificateDirectory"); + } + } +#endif } } From 4a4427eebf871f96283cf55360bd49ed366cff68 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Tue, 28 Jan 2025 15:31:04 -0600 Subject: [PATCH 2/6] Update API based on review; update doc --- .../ref/System.DirectoryServices.Protocols.cs | 10 +++++++- .../ldap/LdapSessionOptions.Linux.cs | 24 +++++++++++++++---- .../ldap/LdapSessionOptions.Windows.cs | 14 ++++++++--- .../tests/DirectoryServicesProtocolsTests.cs | 2 +- .../tests/LdapSessionOptionsTests.cs | 8 ++++--- .../System.Private.CoreLib.Shared.projitems | 2 +- 6 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.DirectoryServices.Protocols/ref/System.DirectoryServices.Protocols.cs b/src/libraries/System.DirectoryServices.Protocols/ref/System.DirectoryServices.Protocols.cs index 579c5ce047fca7..ea0d24e298e804 100644 --- a/src/libraries/System.DirectoryServices.Protocols/ref/System.DirectoryServices.Protocols.cs +++ b/src/libraries/System.DirectoryServices.Protocols/ref/System.DirectoryServices.Protocols.cs @@ -382,8 +382,12 @@ public partial class LdapSessionOptions internal LdapSessionOptions() { } public bool AutoReconnect { get { throw null; } set { } } public string DomainName { get { throw null; } set { } } + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] - public string CertificateDirectory { get { throw null; } set { } } + 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 { } } @@ -404,6 +408,10 @@ 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("android")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")] + [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] public void StartNewTlsSessionContext() { } public bool TcpKeepAlive { get { throw null; } set { } } diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs index 14cb8df47e7171..a5283d331728b3 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; +using System.Runtime.Versioning; namespace System.DirectoryServices.Protocols { @@ -12,10 +13,20 @@ public partial class LdapSessionOptions private bool _secureSocketLayer; /// - /// Specifies the path of the directory containing CA certificates. + /// Specifies the path of the directory containing CA certificates in the PEM format. + /// Multiple directories may be specified by separating with a semi-colon. /// - [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] - public string CertificateDirectory + /// + /// The certificate files are looked up by the CA subject name hash value where that hash can be + /// obtained by using, for example, openssl x509 -hash -noout -in CA.crt. + /// It is a common practice to have the file be a symbolic link to the actual certificate file. + /// + [UnsupportedOSPlatform("android")] + [UnsupportedOSPlatform("browser")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [UnsupportedOSPlatform("windows")] + public string TrustedCertificatesDirectory { get => GetStringValueHelper(LdapOption.LDAP_OPT_X_TLS_CACERTDIR, releasePtr: true); set => SetStringOptionHelper(LdapOption.LDAP_OPT_X_TLS_CACERTDIR, value); @@ -64,8 +75,13 @@ public ReferralChasingOptions ReferralChasing /// /// Create a new TLS library context. + /// Calling this is necessary after setting TLS-based options, such as TrustedCertificatesDirectory. /// - [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] + [UnsupportedOSPlatform("android")] + [UnsupportedOSPlatform("browser")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [UnsupportedOSPlatform("windows")] public void StartNewTlsSessionContext() { SetIntValueHelper(LdapOption.LDAP_OPT_X_TLS_NEWCTX, 0); diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs index 263780ada2e674..b87ded27b47f73 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs @@ -10,8 +10,12 @@ public partial class LdapSessionOptions { private static void PALCertFreeCRLContext(IntPtr certPtr) => Interop.Ldap.CertFreeCRLContext(certPtr); - [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] - public string CertificateDirectory + [UnsupportedOSPlatform("android")] + [UnsupportedOSPlatform("browser")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [UnsupportedOSPlatform("windows")] + public string TrustedCertificatesDirectory { get => throw new PlatformNotSupportedException(); set => throw new PlatformNotSupportedException(); @@ -31,7 +35,11 @@ public bool SecureSocketLayer } } - [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] + [UnsupportedOSPlatform("android")] + [UnsupportedOSPlatform("browser")] + [UnsupportedOSPlatform("ios")] + [UnsupportedOSPlatform("tvos")] + [UnsupportedOSPlatform("windows")] public void StartNewTlsSessionContext() => throw new PlatformNotSupportedException(); public int ProtocolVersion diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs index 3b44e72354658f..626d80267b2015 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs +++ b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs @@ -712,7 +712,7 @@ public void StartNewTlsSessionContext_ThrowsLdapException() { LdapSessionOptions options = connection.SessionOptions; - // To get this to not throw, we need to set CertificateDirectory and have a .crt file in that directory. + // To get this to not throw, we need to use TrustedCertificatesDirectory along with other valid options to connect. Assert.Throws(() => options.StartNewTlsSessionContext()); } } diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/LdapSessionOptionsTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/LdapSessionOptionsTests.cs index 1967ffda17dc82..f341f0b2da3e14 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/LdapSessionOptionsTests.cs +++ b/src/libraries/System.DirectoryServices.Protocols/tests/LdapSessionOptionsTests.cs @@ -767,8 +767,10 @@ public void CertificateDirectoryProperty() using (var connection = new LdapConnection("server")) { LdapSessionOptions options = connection.SessionOptions; - options.CertificateDirectory = "CertificateDirectory"; - Assert.Equal("CertificateDirectory", options.CertificateDirectory); + Assert.Null(options.TrustedCertificatesDirectory); + + options.TrustedCertificatesDirectory = "CertificateDirectory"; + Assert.Equal("CertificateDirectory", options.TrustedCertificatesDirectory); } } @@ -779,7 +781,7 @@ public void CertificateDirectoryProperty_ThrowsPlatformNotSupportedException() using (var connection = new LdapConnection("server")) { LdapSessionOptions options = connection.SessionOptions; - Assert.Throws(() => options.CertificateDirectory = "CertificateDirectory"); + Assert.Throws(() => options.TrustedCertificatesDirectory = "CertificateDirectory"); } } #endif diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 064e2579bdca3c..437655cc294ba1 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -2789,4 +2789,4 @@ - + \ No newline at end of file From 51070ed96e18f025bfd5dac4b61693c2b17879f0 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Tue, 28 Jan 2025 15:57:38 -0600 Subject: [PATCH 3/6] Update test (manually verified on Linux) --- .../tests/DirectoryServicesProtocolsTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs index 626d80267b2015..a46da7a5db1b02 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs +++ b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs @@ -706,14 +706,14 @@ public void TestMultipleServerBind() #if NET [ConditionalFact(nameof(LdapConfigurationExists))] [PlatformSpecific(TestPlatforms.Linux)] - public void StartNewTlsSessionContext_ThrowsLdapException() + public void StartNewTlsSessionContext() { using (var connection = new LdapConnection("server")) { LdapSessionOptions options = connection.SessionOptions; - // To get this to not throw, we need to use TrustedCertificatesDirectory along with other valid options to connect. - Assert.Throws(() => options.StartNewTlsSessionContext()); + // A complete test would be to use TrustedCertificatesDirectory along with other valid options to connect. + options.StartNewTlsSessionContext(); } } From 5c64144ebf59ef1ef3c0cf50f47535c48b860ed6 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Mon, 3 Feb 2025 15:30:17 -0600 Subject: [PATCH 4/6] Address feedback --- .../DirectoryServices/LDAP.Configuration.xml | 42 ++++++-------- .../src/Resources/Strings.resx | 3 + .../ldap/LdapSessionOptions.Linux.cs | 25 +++++---- .../ldap/LdapSessionOptions.Windows.cs | 8 --- .../tests/DirectoryServicesProtocolsTests.cs | 56 ++++++++++++++++--- 5 files changed, 83 insertions(+), 51 deletions(-) diff --git a/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml b/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml index 500a9ccb4e7e04..1a7f36e6f047bd 100644 --- a/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml +++ b/src/libraries/Common/tests/System/DirectoryServices/LDAP.Configuration.xml @@ -1,17 +1,9 @@ -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). -When testing with later of versions of LDAP, the ldapsearch commands below may need to use - - -H ldap://localhost:YOURPORT - -instead of - - -h localhost -p YOURPORT - OPENDJ SERVER ============= @@ -19,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 @@ -32,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 @@ -64,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 ================ @@ -73,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. @@ -90,7 +84,7 @@ Note: @@ -113,15 +107,6 @@ Note: ServerBind,None False - - danmose-ldap.danmose-domain.com - DC=danmose-domain,DC=com - 389 - danmose-domain\Administrator - %TESTPASSWORD% - ServerBind,None - True - ldap.local DC=example,DC=org @@ -132,5 +117,14 @@ Note: true False + + danmose-ldap.danmose-domain.com + DC=danmose-domain,DC=com + 389 + danmose-domain\Administrator + %TESTPASSWORD% + ServerBind,None + True + diff --git a/src/libraries/System.DirectoryServices.Protocols/src/Resources/Strings.resx b/src/libraries/System.DirectoryServices.Protocols/src/Resources/Strings.resx index b63f103619fbdb..1f6c9734a7384c 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/Resources/Strings.resx +++ b/src/libraries/System.DirectoryServices.Protocols/src/Resources/Strings.resx @@ -426,4 +426,7 @@ Only ReferralChasingOptions.None and ReferralChasingOptions.All are supported on Linux. + + The directory '{0}' does not exist. + \ No newline at end of file diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs index a5283d331728b3..5059c40499d5c6 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Linux.cs @@ -2,6 +2,7 @@ // 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 @@ -19,17 +20,25 @@ public partial class LdapSessionOptions /// /// The certificate files are looked up by the CA subject name hash value where that hash can be /// obtained by using, for example, openssl x509 -hash -noout -in CA.crt. - /// It is a common practice to have the file be a symbolic link to the actual certificate file. + /// 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 openssl rehash . or c_rehash . in the directory + /// containing the certificate files. /// - [UnsupportedOSPlatform("android")] - [UnsupportedOSPlatform("browser")] - [UnsupportedOSPlatform("ios")] - [UnsupportedOSPlatform("tvos")] + /// The directory not exist. [UnsupportedOSPlatform("windows")] public string TrustedCertificatesDirectory { get => GetStringValueHelper(LdapOption.LDAP_OPT_X_TLS_CACERTDIR, releasePtr: true); - set => SetStringOptionHelper(LdapOption.LDAP_OPT_X_TLS_CACERTDIR, value); + + set + { + if (!Directory.Exists(value)) + { + throw new DirectoryNotFoundException(SR.Format(SR.DirectoryNotFound, value)); + } + + SetStringOptionHelper(LdapOption.LDAP_OPT_X_TLS_CACERTDIR, value); + } } public bool SecureSocketLayer @@ -77,10 +86,6 @@ public ReferralChasingOptions ReferralChasing /// Create a new TLS library context. /// Calling this is necessary after setting TLS-based options, such as TrustedCertificatesDirectory. /// - [UnsupportedOSPlatform("android")] - [UnsupportedOSPlatform("browser")] - [UnsupportedOSPlatform("ios")] - [UnsupportedOSPlatform("tvos")] [UnsupportedOSPlatform("windows")] public void StartNewTlsSessionContext() { diff --git a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs index b87ded27b47f73..cc73449104adf4 100644 --- a/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs +++ b/src/libraries/System.DirectoryServices.Protocols/src/System/DirectoryServices/Protocols/ldap/LdapSessionOptions.Windows.cs @@ -10,10 +10,6 @@ public partial class LdapSessionOptions { private static void PALCertFreeCRLContext(IntPtr certPtr) => Interop.Ldap.CertFreeCRLContext(certPtr); - [UnsupportedOSPlatform("android")] - [UnsupportedOSPlatform("browser")] - [UnsupportedOSPlatform("ios")] - [UnsupportedOSPlatform("tvos")] [UnsupportedOSPlatform("windows")] public string TrustedCertificatesDirectory { @@ -35,10 +31,6 @@ public bool SecureSocketLayer } } - [UnsupportedOSPlatform("android")] - [UnsupportedOSPlatform("browser")] - [UnsupportedOSPlatform("ios")] - [UnsupportedOSPlatform("tvos")] [UnsupportedOSPlatform("windows")] public void StartNewTlsSessionContext() => throw new PlatformNotSupportedException(); diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs index a46da7a5db1b02..4403cb8a702483 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs +++ b/src/libraries/System.DirectoryServices.Protocols/tests/DirectoryServicesProtocolsTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.DirectoryServices.Tests; using System.Globalization; +using System.IO; using System.Net; using Xunit; @@ -13,6 +14,7 @@ public partial class DirectoryServicesProtocolsTests { internal static bool LdapConfigurationExists => LdapConfiguration.Configuration != null; internal static bool IsActiveDirectoryServer => LdapConfigurationExists && LdapConfiguration.Configuration.IsActiveDirectoryServer; + internal static bool UseTls => LdapConfigurationExists && LdapConfiguration.Configuration.UseTls; internal static bool IsServerSideSortSupported => LdapConfigurationExists && LdapConfiguration.Configuration.SupportsServerSideSort; @@ -704,16 +706,48 @@ public void TestMultipleServerBind() } #if NET - [ConditionalFact(nameof(LdapConfigurationExists))] + [ConditionalFact(nameof(UseTls))] [PlatformSpecific(TestPlatforms.Linux)] public void StartNewTlsSessionContext() { - using (var connection = new LdapConnection("server")) + using (var connection = GetConnection(bind: false)) { - LdapSessionOptions options = connection.SessionOptions; + // We use "." as the directory since it must be a valid directory for StartNewTlsSessionContext() + Bind() to be successful even + // though there are no client certificates in ".". + connection.SessionOptions.TrustedCertificatesDirectory = "."; + + // For a real-world scenario, we would call 'StartTransportLayerSecurity(null)' here which would do the TLS handshake including + // providing the client certificate to the server and validating the server certificate. However, this requires additional + // setup that we don't have including trusting the server certificate and by specifying "demand" in the setup of the server + // via 'LDAP_TLS_VERIFY_CLIENT=demand' to force the TLS handshake to occur. + + connection.SessionOptions.StartNewTlsSessionContext(); + connection.Bind(); + + SearchRequest searchRequest = new (LdapConfiguration.Configuration.SearchDn, "(objectClass=*)", SearchScope.Subtree); + _ = (SearchResponse)connection.SendRequest(searchRequest); + } + } + + [ConditionalFact(nameof(UseTls))] + [PlatformSpecific(TestPlatforms.Linux)] + public void StartNewTlsSessionContext_ThrowsLdapException() + { + using (var connection = GetConnection(bind: false)) + { + // Create a new session context without setting TrustedCertificatesDirectory. + connection.SessionOptions.StartNewTlsSessionContext(); + Assert.Throws(() => connection.Bind()); + } + } - // A complete test would be to use TrustedCertificatesDirectory along with other valid options to connect. - options.StartNewTlsSessionContext(); + [ConditionalFact(nameof(LdapConfigurationExists))] + [PlatformSpecific(TestPlatforms.Linux)] + public void TrustedCertificatesDirectory_ThrowsDirectoryNotFoundException() + { + using (var connection = GetConnection(bind: false)) + { + Assert.Throws(() => connection.SessionOptions.TrustedCertificatesDirectory = "nonexistent"); } } @@ -816,17 +850,17 @@ private static LdapConnection GetConnection(string server) return GetConnection(directoryIdentifier); } - private static LdapConnection GetConnection() + private static LdapConnection GetConnection(bool bind = true) { LdapDirectoryIdentifier directoryIdentifier = string.IsNullOrEmpty(LdapConfiguration.Configuration.Port) ? new LdapDirectoryIdentifier(LdapConfiguration.Configuration.ServerName, fullyQualifiedDnsHostName: true, connectionless: false) : new LdapDirectoryIdentifier(LdapConfiguration.Configuration.ServerName, int.Parse(LdapConfiguration.Configuration.Port, NumberStyles.None, CultureInfo.InvariantCulture), fullyQualifiedDnsHostName: true, connectionless: false); - return GetConnection(directoryIdentifier); + return GetConnection(directoryIdentifier, bind); } - private static LdapConnection GetConnection(LdapDirectoryIdentifier directoryIdentifier) + private static LdapConnection GetConnection(LdapDirectoryIdentifier directoryIdentifier, bool bind = true) { NetworkCredential credential = new NetworkCredential(LdapConfiguration.Configuration.UserName, LdapConfiguration.Configuration.Password); @@ -839,7 +873,11 @@ private static LdapConnection GetConnection(LdapDirectoryIdentifier directoryIde // to LDAP v2, which we do not support, and will return LDAP_PROTOCOL_ERROR connection.SessionOptions.ProtocolVersion = 3; connection.SessionOptions.SecureSocketLayer = LdapConfiguration.Configuration.UseTls; - connection.Bind(); + + if (bind) + { + connection.Bind(); + } connection.Timeout = new TimeSpan(0, 3, 0); return connection; From 1940a2b6fbf06863f817358392a95197bd13204e Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Tue, 4 Feb 2025 10:24:46 -0600 Subject: [PATCH 5/6] Also remove UnsupportedOSPlatformAttribute from ref file --- .../ref/System.DirectoryServices.Protocols.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/libraries/System.DirectoryServices.Protocols/ref/System.DirectoryServices.Protocols.cs b/src/libraries/System.DirectoryServices.Protocols/ref/System.DirectoryServices.Protocols.cs index ea0d24e298e804..3559adc9703731 100644 --- a/src/libraries/System.DirectoryServices.Protocols/ref/System.DirectoryServices.Protocols.cs +++ b/src/libraries/System.DirectoryServices.Protocols/ref/System.DirectoryServices.Protocols.cs @@ -382,10 +382,6 @@ public partial class LdapSessionOptions internal LdapSessionOptions() { } public bool AutoReconnect { get { throw null; } set { } } public string DomainName { get { throw null; } set { } } - [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("android")] - [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] - [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")] - [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] public string TrustedCertificatesDirectory { get { throw null; } set { } } public string HostName { get { throw null; } set { } } @@ -408,10 +404,6 @@ 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("android")] - [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("browser")] - [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("ios")] - [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("tvos")] [System.Runtime.Versioning.UnsupportedOSPlatformAttribute("windows")] public void StartNewTlsSessionContext() { } public bool TcpKeepAlive { get { throw null; } set { } } From 7a11a43457408d2f6c6ec33acc2f14e7c5ab87b9 Mon Sep 17 00:00:00 2001 From: Steve Harter Date: Tue, 4 Feb 2025 14:36:19 -0600 Subject: [PATCH 6/6] Fix a couple test failures --- .../tests/LdapSessionOptionsTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.DirectoryServices.Protocols/tests/LdapSessionOptionsTests.cs b/src/libraries/System.DirectoryServices.Protocols/tests/LdapSessionOptionsTests.cs index f341f0b2da3e14..2a8ab23a16d421 100644 --- a/src/libraries/System.DirectoryServices.Protocols/tests/LdapSessionOptionsTests.cs +++ b/src/libraries/System.DirectoryServices.Protocols/tests/LdapSessionOptionsTests.cs @@ -29,6 +29,7 @@ public void ReferralChasing_Set_GetReturnsExpected_On_Windows(ReferralChasingOpt } [Theory] + [ActiveIssue("https://github.com/dotnet/runtime/issues/112146")] [PlatformSpecific(TestPlatforms.Linux)] [InlineData(ReferralChasingOptions.None)] [InlineData(ReferralChasingOptions.All)] @@ -769,8 +770,8 @@ public void CertificateDirectoryProperty() LdapSessionOptions options = connection.SessionOptions; Assert.Null(options.TrustedCertificatesDirectory); - options.TrustedCertificatesDirectory = "CertificateDirectory"; - Assert.Equal("CertificateDirectory", options.TrustedCertificatesDirectory); + options.TrustedCertificatesDirectory = "."; + Assert.Equal(".", options.TrustedCertificatesDirectory); } }