Authentication using Secure LDAP (ie LDAPS) in C# - c#

I am able to validate a user against Active Directory using following code and it works fine:
bool authentic = false;
PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain);
// validate the credentials against domain
authentic = pc.ValidateCredentials(userName, password);
However the username and password are being sent to network in plain text. I was told to use ContextOptions.SecureSocketLayer with ValidateCredentials but it throws exception
The server cannot handle directory requests.
Can anyone please help me with this so that I can authenticate with LDAP server in a secure way.
Thanks

I need to know this too. I think (but haven't tested) that ContextOptions.Negotiate will use SSL. You may need to force the port to 636 e.g.
PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain + ":636", ContextOptions.Negotiate);
or
PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain + ":636", ContextOptions.Negotiate | ContextOptions.Signing | ContextOptions.Sealing);
EDIT
I have this working now (as above). The only problem I had was I was trying to connect with IP address. This didn't work but I didn't know why until I looked in windows Event Viewer (on client) and there were errors about the certificate. When I changed to servername that matched the certificate, it worked.

LDAP clients should always use SSL or a non-secure connection promoted to a secure connection with the StartTLS extended operation - modern, professional-quality servers have the capability to reject operations on non-secure connections, though legacy servers often lack this feature. LDAP is encoded, not encrypted, for transmission.
Check with the directory server administrator to ensure that SSL (or StartTLS) is supported by the server, check that the certificates involved are valid. Consider using openssl s_client -connect host:port to validate the connection.

Related

Modify an attribute value in Active Directory via ldaps

I executed the code as below to modify an attribute value in Active Directory via ldaps.It worked properly.In addition, I found the packets were encrypted when I analyzed the packets captured by tcpdump via WireShark.
using (DirectoryEntry entry = new DirectoryEntry("LDAP://192.168.109.4:636/OU=People,DC=dev,DC=com", "dev\\user", "password"))
{
entry.Properties["description"].Value = "a new description";
entry.CommitChanges();
entry.Close();
}
However, I have one question.I guess that the statement below is requried to encrypt packets via ldaps.
entry.AuthenticationType = AuthenticationTypes.SecureSocketsLayer;
In this case, it worked well without the statement as above.
Does anyone know the reason?
Did you see the SSL handshake when the connection opened? Usually SSL won't even work when you're accessing it with an IP address. It can also encrypt using Kerberos, which will work on port 389 using an IP address, although you usually have to specify AuthenticationTypes.Sealing for that.
However, it does know that port 636 is the LDAPS port, so if you specify port 636, it will automatically do the SSL handshake.
You can also exclude the port and specify AuthenticationTypes.SecureSocketsLayer, and it will automatically connect via port 636:
new DirectoryEntry(
"LDAP://dev.com/OU=People,DC=dev,DC=com",
"dev\\user", "password",
AuthenticationTypes.Secure | AuthenticationTypes.SecureSocketsLayer
)
AuthenticationType is Secure by default.
SSL is a server-side configuration so the LDAP Server Admin must have enabled SSL.

Default .Net LdapConnection SSL validation

When looking for a way to implement server certificate validation when accessing AD using LDAP with SSL, I have come across the following answer to one of the questions here on Stack Overflow - https://stackoverflow.com/a/41013779/6179181.
In the answer it is stated that:
You should not write validation yourself. Certificate validation is tricky business, and it's already done for you. Use the built-in stuff...
Is my understanding correct that there is a default algorithm for validating server SSL certificates? For example, will the following code perform implicit validation of the server's SSL certificate? If so, where I could read more about it (I did not find much information on MSDN)?
LdapConnection _connection = new LdapConnection(new LdapDirectoryIdentifier(m_DomainName, m_PortNo));
_connection.AuthType = AuthType.Basic;
_connection.Credential = new NetworkCredential(m_UserName, m_Password);
_connection.SessionOptions.SecureSocketLayer = true;
_connection.Bind();
EDIT:
Is the root CA that issued the certificate to the server validated against trusted root CAs in the local certificates store implicitly in the code above?

Sending e-mail through C#, Exchange and EWS Managed API gives error 407: Request failed - Proxy authentication required. Why?

I am writing a Windows forms application based on C# and the EWS Managed API 2.2.0 (Microsoft.Exchange.WebServices.dll assembly) for a client of mine who works on a company.
The app's logic is simple: My client puts his e-mail address "sender#ABC.domain.com" and the recipient's e-mail address "recipient#contoso.com" and the app sends an HTML-based e-mail to the recipient.
The app must connect to the Exchange Server of my client's company, authenticate my client to Exchange as a Windows domain user (using Windows domain authentication) (client is already logged in Windows with his username "sender" and as this Windows user makes the request) and then, send the E-Mail through his mailbox "sender#ABC.domain.com".
I have wrote the above code so far which seems to be correct, but it gives me this error: "The request failed. The remote server returned an error: (407). Proxy Authentication required"
try{
//Initialize and bound 'service' to the latest known version of Exchange
ExchangeService service = new ExchangeService();
//Use the Windows logged-in user domain account credentials:
//Is this correct / is this enough???
service.UseDefaultCredentials = true;
ServicePointManager.ServerCertificateValidationCallback = myValidationCallBackFunction;
service.AutodiscoverUrl("sender#ABC.domain.com", myRedirectionCallback);
MessageBox.Show("Autodiscover Exchange URL found:" + service.Url.ToString());
//Impersonation test - just in case - but this is not make any difference in the error
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, "sender#ABC.domain.com");
//Initialize and set an EmailMessage
EmailMessage message = new EmailMessage(service);
message.Subject = "HelloWorld";
string emailMessageBody = "<!DOCTYPE html><html> ... </html>";
message.Body = new MessageBody(BodyType.HTML, emailMessageBody);
email.ToRecipients.Add("recipient#contoso.com");
message.SendAndSaveCopy();
MessageBox.Show("E-Mail sent successfully");
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex.Message);
}
My client uses Windows 10. His PC is part of a Windows domain with url "XYZ.domain.com" and his username on Windows is "sender". He also has an Outlook app installed in his PC which connects perfectly to the Exchange Server on his company's network...
I am totally amateur with Windows domain accounts / authentication. Is this a Windows domain authentication problem? Is this an Exchange conection settings problem? Or the problem is in my code in the authentication section (do I have to add something more)?
I found a part of solution by my own!
Now my code works fine.
The problem was in the Proxy Settings in Windows 10 on my client's PC. For some reason he had Manual proxy setup enabled but he didn't know it. When I found it and disabled it, then the e-mail was sent fine.
I'm saying that I found "a part" of solution because:
1) The Outlook 365 works fine with the on premises Exchange Server 2010, even if I have the proxy settings enabled or not. But my app works only with proxy settings disabled. Does anyone know how this is explained? Why Outlook works fine with both ways?
2) If the IT of the company of my client forces him to have always enabled the proxy settings, how can I bypass this error? Do I have to pass somehow the User's credentials (with or without password)? If, yes, then how can I do this with C#?
I think it works fine both ways because your customer has "Don't use the proxy server for local (intranet) addresses" enabled in Windows 10 Proxy Settings. That would explain why the proxy is bypassed when connecting to the on-premise Exchange Server.
It doesn't explain however why that doesn't happen when your app connects to the local Exchange Server (?)
You can configure EWS to use a System.Net.WebProxy like this:
WebProxy myProxy = new WebProxy("http://myproxy", 8080)
{
BypassProxyOnLocal = true,
Credentials = new NetworkCredentials("username", "password)
};
exchangeService.WebProxy = myProxy;
What credentials you use depends on your client's Proxy infrastructure. If it requires a domain account you can set myProxy.UseDefaultCredentials = true to pass in the current user.
Hth

How to validate server SSL certificate for LDAP+SSL connection

Our application works with Active Directory users and groups. We are using LDAP on port 389 for Active Directory operations. Now, one of our clients want us add an option for using LDAP + SSL for Active Directory communication.
They told us that they have a local CA installed on their domain and using self signed certificate for LDAPS. They also told us that they will provide the certificate, no mutual-trust needed and we should use Windows certificate store.
I have developed a test application for LDAP+SSL operations and saw that server sends its certificate when a client initiates an LDAP+SSL connection. I can establish the connection only by returning true from the server certificate verification method.
The questions are;
- Which certificate (root, the ceritificate used for LDAP+SSL...) should the customer give us?
What should be the format of the certificate for working on .Net environment?
How should I verify the server's certificate when connecting the server?
What they mean by "we should use Windows certificate store"? Do they want us add the server's certificate automatically to trusted certificate store of the local machine?
Sample code I used for LDAP+SSL connection,
LdapConnection _connection = new LdapConnection(new LdapDirectoryIdentifier(m_DomainName, m_PortNo));
_connection.Timeout = TimeSpan.FromMinutes(10);
_connection.AuthType = AuthType.Basic;
_connection.Credential = new NetworkCredential(m_UserName, m_Password);
_connection.SessionOptions.ProtocolVersion = 3;
_connection.SessionOptions.SecureSocketLayer = true;
_connection.SessionOptions.VerifyServerCertificate = (ldapCon, serverCertificate) =>
{
//TODO: Verify server certificate
return true;
};
_connection.SessionOptions.QueryClientCertificate = (con, trustedCAs) => null;
_connection.Bind();
Which certificate (root, the ceritificate used for LDAP+SSL...) should the customer give us?
The root certificate that signed the LDAP server cert. They can also give you the whole chain in advance, but that will be sent during TLS handshake anyway. You only need to have the root cert in advance.
What should be the format of the certificate for working on .Net environment?
Anything that you can import into certmgr.msc. Pfx is the usual choice on Windows.
How should I verify the server's certificate when connecting the server?
You should not write validation yourself. Certificate validation is tricky business, and it's already done for you. Use the built-in stuff (also see below).
What they mean by "we should use Windows certificate store"? Do they want us add the server's certificate automatically to trusted certificate store of the local machine?
Yes. They send you the root cert they used for signing the ldap server cert, which you can then import as a trusted root. Once this is done, you don't need to do any manual validation, it will just work™ :) with valid certificates and will not work with invalid ones.
Note that once you add their root cert as trusted, they can forge any server certificate for the client their root is installed on, and anything they sign will be considered valid on that client.
Bonus: adding semi-custom validation and debugging certificate errors
One problem that you may face is that error messages are not very helpful. If the certificate cannot be validated, you will get a very generic error message that has no hint about the actual problem. You may want to hook into the validation process for other reasons too.
For this purpose, you can define your own validation:
private bool VerifyServerCertificate(LdapConnection ldapConnection, X509Certificate certificate)
{
X509Certificate2 certificate2 = new X509Certificate2( certificate );
return certificate2.Verify();
}
And then add it to the ldap connection:
_connection.SessionOptions.VerifyServerCertificate =
new VerifyServerCertificateCallback( VerifyServerCertificate );
This way you can catch exceptions on Verify() etc. But again, if the certificate is valid (can be verified by the client), this is not strictly needed, it's done automatically anyway. You only need this if you want something not implemented, like for example you could just return true in VerifyServerCertificate to accept any cert including the invalid ones (this would be a Bad Idea and makes a secure connection useless, but may be good for debugging, etc).
Another thing you could implement in this method is certificate pinning for additional security, but that's beyond the scope of this answer.
BTW: Since introduction of the StartTLS extended operation for LDAP v3 in May 2000 (RFC 2830) LDAPS (made for LDAP v2) is deprecated.

SSL Socket between .Net and Java with client authentication

I am trying to create an SSL Socket Server/Client between .NET and Java. In this case, my SSL Socket Server will run in .net and the client runs in Java under Linux. My problem is that the connection fails during the handshaking, specifically when the server request a certificate from the client, the client is unable to send something back and the connection fails.
In .net I am using sslStream to establish the connection and on Java I am using the standard SSLSocket. Some code snippets are below, but this is what I have so far:
On the server side (Windows), I have a private certificate in the Personal/Certificates folders under MMC. I have a public certificate from the client in the Trusted People/Certificates. Both certificates were issued by the same CA. The certificate chain for both certificates have multiple levels, but it is the same for both. The root level certificate in the chain is also installed in the trusted Certification Authorities/Certificates folder.
On the client side (Linux), I have a keystore that contains the private certificate that matches the public certificate installed at the server. I have a trust store that contains the public certificate from the server, matching the server's private one.
On the server side (.net) I am using a Socket that does an asynchronous read and then it gets wrapped into an SSLStream, the code snippet is like this:
NetworkStream ns = new NetworkStream(socket, false);
SslStream ssl = new SslStream(ns, true);
ssl.AuthenticateAsServer(serverCertificate, true, SslProtocols.Default, true);
The client code is pretty much standard code:
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
InetAddress addr = InetAddress.getByName(servername);
SSLSocket socket = (SSLSocket) factory.createSocket(addr,port);
socket.setUseClientMode(true);
socket.setNeedClientAuth(true);
socket.setWantClientAuth(true);
socket.startHandshake();
os = new DataOutputStream(socket.getOutputStream());
is = new DataInputStream(socket.getInputStream());
byte[] outBuf = new byte[50];
os.write("SEND SOMETHING".getBytes("UTF-8"));
is.read(outBuf);
In java I have set the proper varialbes to point to the trust and key store with their password.
Now, following the standard SSL Handshake, this is what happens:
ClientHello
ServerHello
Server sends public certificate
Client matches the public certificate with the one on the trust store
Server sends the Certificate request
With the certificate request the server sends a list of valid CAs, on this list only the my root CA is sent (among a long list of other well known CAs.).
Client certificate is null.
Server receives a null certificate from the client, thus closes the connection.
And that is it, the client won't send a valid certificate back to the server. I have some questions on this:
Has anybody experienced something like this?
Regarding that list of CAs sent by the server (Windows), How does .net determine what to send to the client? Is there a way to modify that list?
Do I need to send the all the authorities in the chain used to sign my certificate in that list of CAs? or is the Root one enough?
Am I missing something on either side of my code?
Any help will be greatly appreciated it.
In
The following two statements are useless on the client side (although they shouldn't hurt):
socket.setNeedClientAuth(true);
socket.setWantClientAuth(true);
The fact that you see the Certificate Request message and the Client Certificate message shows that the server is configured properly.
The most likely cause that comes to mind for the absence of certificate in the client certificate message is that the keystore (on the client side) might not be configured properly. You may be interested in this answer to make sure that your client key store is configured properly. More specifically, you need to make sure that the private key for your client certificate was imported in the same alias as the certificate chain (and that it's the chain going back to a CA advertised in the Certificate Request message).
(Regarding the rest of your question, I'm not sure how to modify the CA list sent by the server when using SslStream in C#. This earlier question would seem to suggest there is no solution, although newer versions of .Net may have addresses the issue since this question was asked. I haven't been able to find anything that would do it by looking at the SslStream API documentation and related classes, but this doesn't mean it doesn't exist.)

Categories

Resources