C# Cannot connect to AD using LDAPS - asp.net-core

My requirement was to change the user password of AD. So, I created the LDAP SSL secure connection on the AD domain server by following https://bl.ocks.org/magnetikonline/0ccdabfec58eb1929c997d22e7341e45 successfully.
Using the ldp.exe tool (on the same AD server) I am able to connect with the SSL. This means LDAPS is enabled on the AD server.
Now I am trying to connect it from the ASP.NET Core application using the library Novell.Directory.Ldap which is on client-side using the following code:
public LdapConnection GetLDAPConnection(IOptions<ADConfiguration> _settings)
{
LdapConnection connection = new LdapConnection { SecureSocketLayer = true };
connection.Connect(_settings.Value.DomainIPAddress, _settings.Value.Port); //port is 636
connection.Bind(_settings.Value.AdminDn, _settings.Value.Password);
if (connection.Bound)
{
return connection;
}
return null;
}
The Connect method is throwing this error:
System.Security.Authentication.AuthenticationException: 'The remote certificate was rejected by the provided RemoteCertificateValidationCallback.'
Does the client machine also have settings for SSL? Or what else I am missing? Please help

I suspect your problem is using the IP address of the domain controller: _settings.Value.DomainIPAddress
SSL/TLS has two purposes: to encrypt the traffic, and to validate that the server is actually the server you want to be talking to. To address the second purpose, the domain name you use to connect must match the domain name in the certificate. In your case, when it validates the certificate, it sees that you connected to, let's say, 10.0.0.1, but the certificate it gets from the server says it is example.com and the validation fails because it doesn't match.
You will have to either:
Change _settings.Value.DomainIPAddress to the domain name used in the certificate. If you don't have DNS setup for that domain name, you could add an entry in your hosts file.
Tell LdapConnection to ignore certificate errors. The data will still be encrypted, but it won't validate the certificate (domain mismatch, expired cert, etc.). This is not recommended for a production application, but there is an example of that here: https://stackoverflow.com/a/67818854/1202807

Below code worked for me to connect to AD using LDAPS
ldapConnection = new LdapConnection(new LdapDirectoryIdentifier("your.LDAPSserver.com", 636));
var networkCredential = new NetworkCredential("UsernameWithoutDomain", "yourPassword", "AD.yourDOMAIN.com");
ldapConnection.SessionOptions.SecureSocketLayer = true;
ldapConnection.SessionOptions.ProtocolVersion = 3;
ldapConnection.SessionOptions.VerifyServerCertificate = new VerifyServerCertificateCallback(ServerCallback);
ldapConnection.AuthType = AuthType.Negotiate;
ldapConnection.Bind(networkCredential);
SearchRequest Srchrequest = new SearchRequest("CN=Users,DC=AD,DC=YOURCOMPANY,DC=COM", "mail=useremail#company.com", System.DirectoryServices.Protocols.SearchScope.Subtree);
SearchResponse SrchResponse = (SearchResponse)ldapConnection.SendRequest(Srchrequest);
// ServerCallback
private static bool ServerCallback(LdapConnection connection, X509Certificate certificate)
{
return true;
}
Surprisingly it is also working when I am not using networkCredential and just using ldapConnection.Bind(); Seems it is using my local credentials as default on my local machine.

Related

Fail to establish SSL connection unless user is in Administrators group

It is a .NET 6 project. The certificate is imported to the Local Computer store from a pfx file.
Using the following code, skipping the irrelevant parts, everything works fine when the service account is added to the local Administrators group.
var certStore = new X509Store(storeName, storeLocation);
certStore.Open(OpenFlags.ReadOnly);
var _clientCertificate = certStore.Certificates
.Find(X509FindType.FindByThumbprint, thumbprint, false)
.FirstOrDefault();
...
BasicHttpsBinding binding = new BasicHttpsBinding(BasicHttpsSecurityMode.Transport);
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
var client = new Client(binding, endpoint);
client.ClientCredentials.ClientCertificate.Certificate = _clientCertificate;
...
When the account is not in the local Administrators' group the following exception is thrown:
System.ServiceModel.Security.SecurityNegotiationException: Could not establish trust relationship for the SSL/TLS secure channel with authority 'other.service.com'.
---> System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
---> System.ComponentModel.Win32Exception (0x8009030D): The credentials supplied to the package were not recognized
at System.Net.SSPIWrapper.AcquireCredentialsHandle(ISSPIInterface secModule, String package, CredentialUse intent, SCHANNEL_CRED* scc)
at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(CredentialUse credUsage, SCHANNEL_CRED* secureCredential)
at System.Net.Security.SslStreamPal.AcquireCredentialsHandleSchannelCred(SslStreamCertificateContext certificateContext, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
at System.Net.Security.SslStreamPal.AcquireCredentialsHandle(SslStreamCertificateContext certificateContext, SslProtocols protocols, EncryptionPolicy policy, Boolean isServer)
What am I missing here?
As far as I know, there may be the following reasons:
When you say that you are not in the local administrator group, the error will be because of the administrator and general members have different permissions. You can try to put the user in the administrator to try again, if successful, this is the problem.
Validate the Web Sites SSL Certificate is Trusted. If the SSL certificate is not trusted, you will need to install the SSL certificate’s root certificate. You can review the case for more solutions.
Hope it helps.

Kestrel Fails TLS Handshake after Attempt to Download Intermediate Certificate Fails

Kestrel's web server is timing out, saying Connection Closed, after loading a publicly-signed SSL Certificate.
Background - we have a docker container that hosts a dotnet 3.1 webapi/react app, where the user can upload a custom SSL certificate. The PKCS#12 certificate is stored in our database and bound at startup using .ConfigureKestrel((context,options)) and options.ConfigureHttpsDefaults(listenOptions=>{listenOptions.ServerCertificate = certFromDatabase; }). This has been working flawless.
However, the problem now is that a user is attempting to run this app in a restrictive firewalled environment and is receiving HTTP connection closed errors when attempting to access Kestrel immediately after loading a new certificate and restarting the app.
Whenever Kestrel receives an incoming request, it begins attempting to download the intermediate certificate from the certificate's CA's public CDN repository via http on port 80. It appears to be using the URL from the Authority Information Access portion of the certificate. Since the firewall is blocking this, it retries repeatedly for about 20 seconds, during which time the client's TLS handshake sits waiting on a server response. When the server eventually fails to fetch the intermediate certificate, it cancels the TLS handshake and closes the connection.
I can't figure out why it's attempting to download this certificate, considering the same certificate is embedded in the PKCS#12 PFX bundle that is bound to Kestrel. Am I supposed to load either the root CA or intermediate into the CA trust folder in file system? (Ex. /usr/local/share/ca-certificates/ - I can't load the intermediate there, only the CA?)
public static IWebHost BuildFullWebHost(string[] args)
{
var webHostBuilder = GetBaseWebHostBuilder(args);
return webHostBuilder
.ConfigureAppConfiguration((context, builder) => { [...] })
.ConfigureLogging((hostingContext, logging) => { [...] })
.UseStartup<Startup>()
.ConfigureKestrel((context, options) =>
{
var sp = options.ApplicationServices;
using (var scope = sp.CreateScope())
{
var dbContext = scope.ServiceProvider.GetService<DbContext>();
var cert = Example.Services.HttpsCertificateService.GetHttpsCert(dbContext);
//this returns a new X509Certificate2(certificate.HttpsCertificate, certificate.Password);
options.ConfigureHttpsDefaults(listenOptions =>
{
listenOptions.ServerCertificate = cert;
listenOptions.CheckCertificateRevocation = false;
});
}
})
.Build();
}
Not a great solution, but upgrading to .NET 5.0 resolved the issue. It seems that in .NET 5.0, Kestrel attempts to fetch the certificate chain during initial application startup only (and fails). Subsequent incoming HTTP requests don't trigger the fetch process and requests are served as expected.

Client certificate has different thumprint via ARR and AuthorizationContext

I am currently working on a prototype for a WCF service that will make use of client-certificate authentication. We would like to be able to directly publish our application to IIS, but also allow SSL offloading using IIS ARR (Application Request Routing).
After digging through the documentation, I have been able to test both configurations successfully. I am able to retrieve the client certificate used to authenticate from:
X-Arr-ClientCert - the header that contains the certificate when using ARR.
X509CertificateClaimSet - when published directly to IIS, this is how to retrieve the Client Certificate
To verify that the request is allowed, I match the thumbprint of the certificate to the expected thumbprint that is configured somewhere. To my surprise, when getting the certificate through different methods, the same certificate has different thumbprints.
To verify what is going on, I have converted the "RawData" property on both certificates to Base64 and found that it's the same, except that in the case of the X509CertificateClaimSet, there are spaces in the certificate data, while in the case of ARR, there are not. Otherwise, both strings are the same:
My question:
Has anyone else run into this, and can I actually rely on thumbprints? If not, my backup plan is to implement a check on Subject and Issuer, but I am open to other suggestions.
I have included some (simplified) sample code below:
string expectedThumbprint = "...";
if (OperationContext.Current.ServiceSecurityContext == null ||
OperationContext.Current.ServiceSecurityContext.AuthorizationContext.ClaimSets == null ||
OperationContext.Current.ServiceSecurityContext.AuthorizationContext.ClaimSets.Count <= 0)
{
// Claimsets not found, assume that we are reverse proxied by ARR (Application Request Routing). If this is the case, we expect the certificate to be in the X-ARR-CLIENTCERT header
IncomingWebRequestContext request = WebOperationContext.Current.IncomingRequest;
string certBase64 = request.Headers["X-Arr-ClientCert"];
if (certBase64 == null) return false;
byte[] bytes = Convert.FromBase64String(certBase64);
var cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(bytes);
return cert.Thumbprint == expectedThumbprint;
}
// In this case, we are directly published to IIS with Certificate authentication.
else
{
bool correctCertificateFound = false;
foreach (var claimSet in OperationContext.Current.ServiceSecurityContext.AuthorizationContext.ClaimSets)
{
if (!(claimSet is X509CertificateClaimSet)) continue;
var cert = ((X509CertificateClaimSet)claimSet).X509Certificate;
// Match certificate thumbprint to expected value
if (cert.Thumbprint == expectedThumbprint)
{
correctCertificateFound = true;
break;
}
}
return correctCertificateFound;
}
Not sure what your exact scenario is, but I've always liked the Octopus Deploy approach to secure server <-> tentacle (client) communication. It is described in their Octopus Tentacle communication article. They essentially use the SslStream class, self-signed X.509 certificates and trusted thumbprints configured on both server and tentacle.
-Marco-
When setting up the test again for a peer review by colleagues, it appears that my issue has gone away. I'm not sure if I made a mistake (probably) or if rebooting my machine helped, but in any case, the Thumbprint now is reliable over both methods of authentication.

Certificate based authentication in WCF

I am trying to understand certificate based authentication using the msdn sample https://msdn.microsoft.com/en-us/library/ms731074(v=vs.90).aspx
This is the server code:
WSHttpBinding binding = new WSHttpBinding();
binding.Security.Mode = SecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
// Create the URI for the endpoint.
Uri httpUri = new Uri("https://localhost/Calculator");
// Create the service and add an endpoint.
ServiceHost myServiceHost = new ServiceHost(typeof(ServiceModel.Calculator), httpUri);
myServiceHost.AddServiceEndpoint(typeof(ServiceModel.ICalculator), binding, "");
// Open the service.
myServiceHost.Open();
Console.WriteLine("Listening...");
Console.ReadLine();
// Close the service.
myServiceHost.Close();
This is the client code I wrote:
ChannelFactory<ICalculator> factory = null;
WSHttpBinding binding = new WSHttpBinding();
binding.Security.Mode = SecurityMode.Transport;
binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
EndpointAddress address = new EndpointAddress("https://localhost/Calculator");
factory = new ChannelFactory<ICalculator>(binding, address);
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Ssl3;
factory.Credentials.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySubjectName, "sroger");
ICalculator channel = factory.CreateChannel();
int y = channel.add(9, 8);
I am getting the following exception:
An unhandled exception of type 'System.ServiceModel.CommunicationException' occurred in mscorlib.dll
Additional information: An error occurred while making the HTTP request to https://localhost/Calculator. This could be due to the fact that the server certificate is not configured properly with HTTP.SYS in the HTTPS case. This could also be caused by a mismatch of the security binding between the client and the server.
I am running both client and server from the same machine. And "sroger" is the certificate in my current user\ personal\certificates which corresponds to my machine name..
Not sure what to do from here..Any thoughts?
In the server code what certificate server uses?
Thanks
Gulumal.
https://msdn.microsoft.com/en-us/library/ms731074(v=vs.90).aspx example you used is incomplete.
Consuming https wcf service requires a valid server certificate to work, in your case both client and server certificates are required.
This is because both client and server need to trust each other in a HTTPS connection.
To get started, read https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/message-security-with-mutual-certificates which is a more complete example that includes specifying certificate to authenticate the service.
For a hosted WCF library via https to work you need to do the following in order:
Configure the port with an X.509 certificate (which has been
answered in
webHttpBinding with certificate)
From your server, create certificate request for common name of your
server fully qualified domain name, or at-least including a DNS subjectAltName of your server fully qualified domain name.
(there are different ways to do this, you may already know this
though)
Issue certificate and install certificate on your server
Grab application id from assembly file of your App that hosts WCF
library (i.e [assembly:
Guid("5870aeed-caca-4734-8b09-5c0615402bcf")]) Grab the certificate
thumbprint by viewing certificate properties.
As administrator, open
CMD and run this command to bind X.509 certificate to the port used
by your app on server
netsh http add sslcert ipport=0.0.0.0:443 certhash= appid={} certstorename=MY
netsh http add iplisten ipaddress=0.0.0.0:443
Add this to your server code:
myServiceHost.Credentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySerialNumber, "<certificate thumbprint>");
In your client code, reference your server address by fully qualified domain name that certificate that is specified as certificate Common Name or subject Alt Name

ISAPI Filter LDAP Authentication Error on DMZ Server

I am writing an ISAPI filter for a web server that we have running in a DMZ. This ISAPI filter needs to connect to our internal domain controllers to authenticate against Active Directory. There is a rule in the firewall to allow traffic from the DMZ server to our domain controller on port 636 and the firewall shows that the traffic is passing through just fine. The problem lies in the ldap_connect() function. I am getting an error 0x51 Server Down when attempting to establish the connection. We use the domain controllers IP address instead of the DNS name since the web server's outside the domain.
ISAPI LDAP connection code:
// Set search criteria
strcpy(search, "(sAMAccountName=");
strcat(search, username);
strcat(search, ")");
// Set timeout
time.tv_sec = 30;
time.tv_usec = 30;
// Setup user authentication
AuthId.User = (unsigned char *) username;
AuthId.UserLength = strlen(username);
AuthId.Password = (unsigned char *) password;
AuthId.PasswordLength = strlen(password);
AuthId.Domain = (unsigned char *) domain;
AuthId.DomainLength = strlen(domain);
AuthId.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
// Initialize LDAP connection
ldap = ldap_sslinit(servers, LDAP_SSL_PORT, 1);
if (ldap != NULL)
{
// Set LDAP options
ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, (void *) &version);
ldap_set_option(ldap, LDAP_OPT_SSL, LDAP_OPT_ON);
// Make the connection
//
// FAILS HERE!
//
ldap_response = ldap_connect(ldap, &time);
if (ldap_response == LDAP_SUCCESS)
{
// Bind to LDAP connection
ldap_response = ldap_bind_s(ldap, (PCHAR) AuthId.User, (PCHAR) &AuthId, LDAP_AUTH_NTLM);
}
}
// Unbind LDAP connection if LDAP is established
if (ldap != NULL)
ldap_unbind(ldap);
// Return string
return valid_user;
servers = <DC IP Address>
I have tested this code on my local machine that is within the same domain as AD, and it works, both LDAP and LDAP over SSL. We have a server certificate installed on our domain controller from the Active Directory Enrollment Policy but I read elsewhere that I might need to install a client certificate as well (for our web server). Is this true?
Also, we have a separate wordpress site running on the same DMZ web server that connects to LDAP over SSL just fine. It uses OpenLDAP through PHP to connect and uses the IP address of our domain controllers to connect. We have an ldap.conf file that with a line of code: TLS_REQCERT never. Is there a way to mimic this effect in Visual C with what I'm trying to do for the ISAPI filter? Hoping this is a programming issue more than a certificate issue. If this is out of the realm of programming, please let me know or redirect me to a better place to post this.
Thanks!
Solved the problem by adding the CA to the certificate store on the web server. The CA was never copied over before.