Using StartTLS with LDAP from System.DirectoryServices - c#

I'm trying to connect to an LDAP server which requires StartTLS, but having no luck - whenever I use either the SessionOptions.StartTransportLayerSecurity(..) or set SessionOptions.SecureSocketLayer to true, I get exceptions.
Here's the code I'm using:
using (var connection = new LdapConnection(new LdapDirectoryIdentifier(config.LdapServer, config.Port, false, false)))
{
connection.SessionOptions.ProtocolVersion = 3;
connection.Credential = new NetworkCredential(config.BindDN, config.BindPassword);
connection.SessionOptions.VerifyServerCertificate += (conn, cert) => {return true;};
connection.AuthType = AuthType.Basic;
//connection.SessionOptions.SecureSocketLayer = true;
connection.SessionOptions.StartTransportLayerSecurity(null); // throws here, same if done after bind.
connection.Bind();
... do stuff with connection
}
The resulting exception is "TlsOperationException: An unspecified error occurred", which happens when invoking the StartTransportLayerSecurity method.
I've tested the code against both and OpenLDAP server and Active Directory, but neither works.
Does anyone know how to get StartTLS working with System.DirectoryServices?

There used to be a fair amount of subtle LDAP stack incompatibilities in the wild, which could still apply to the potentially legacy scenario your customer might be using.
The following are the most commonly encountered issues regarding incompatibilities between OpenLDAP and Microsoft's LDAP stack (I'll amend and/or replace these links once more info is available):
The OpenLDAP StartTLS issues (ITS#3037) (summarized in On getting OpenLDAP and Windows LDAP to interop) have triggered a respective hotfix:
You cannot send Start TLS requests from a computer that is running Windows Server 2003 or Windows XP or Windows Vista to a server that is running OpenLDAP Software
An extended operation that is sent to an LDAP server by API over the LDAP service causes a protocol error
Obviously, updating either OpenLDAP and/or Windows (ideally both of course) should remedy these issues, if they turn out to be the culprit here.
Good luck!

Please read this topic:
Binding over a TLS/SSL Encrypted Connection
Example 19. Binding to an ADAM instance on secure port 50001 using Basic authentication and SSL/TLS
string hostNameAndSSLPort = "sea-dc-02.fabrikam.com:50001";
string userName = "cn=User1,cn=AdamUsers,cn=ap1,dc=fabrikam,dc=com";
string password = "adamPassword01!";
// establish a connection
LdapConnection connection = new LdapConnection(hostNameAndSSLPort);
// create an LdapSessionOptions object to configure session
// settings on the connection.
LdapSessionOptions options = connection.SessionOptions;
options.ProtocolVersion = 3;
options.SecureSocketLayer = true;
connection.AuthType = AuthType.Basic;
NetworkCredential credential =
new NetworkCredential(userName, password);
connection.Credential = credential;
try
{
connection.Bind();
Console.WriteLine("\nUser account {0} validated using " +
"ssl.", userName);
if (options.SecureSocketLayer == true)
{
Console.WriteLine("SSL for encryption is enabled\nSSL information:\n" +
"\tcipher strength: {0}\n" +
"\texchange strength: {1}\n" +
"\tprotocol: {2}\n" +
"\thash strength: {3}\n" +
"\talgorithm: {4}\n",
options.SslInformation.CipherStrength,
options.SslInformation.ExchangeStrength,
options.SslInformation.Protocol,
options.SslInformation.HashStrength,
options.SslInformation.AlgorithmIdentifier);
}
}
catch (LdapException e)
{
Console.WriteLine("\nCredential validation for User " +
"account {0} using ssl failed\n" +
"LdapException: {1}", userName, e.Message);
}
catch (DirectoryOperationException e)
{
Console.WriteLine("\nCredential validation for User " +
"account {0} using ssl failed\n" +
"DirectoryOperationException: {1}", userName, e.Message);
}
And the next example show "How to use TLS to authenticate and perform a task"
string hostOrDomainName = "fabrikam.com";
string userName = "user1";
string password = "password1";
// establish a connection to the directory
LdapConnection connection = new LdapConnection(hostOrDomainName);
NetworkCredential credential =
new NetworkCredential(userName, password, domainName);
connection.Credential = credential;
connection.AuthType = AuthType.Basic;
LdapSessionOptions options = connection.SessionOptions;
options.ProtocolVersion = 3;
try
{
options.StartTransportLayerSecurity(null);
Console.WriteLine("TLS started.\n");
}
catch (Exception e)
{
Console.WriteLine("Start TLS failed with {0}",
e.Message);
return;
}
try
{
connection.Bind();
Console.WriteLine("Bind succeeded using basic " +
"authentication and SSL.\n");
Console.WriteLine("Complete another task over " +
"this SSL connection");
TestTask(hostName);
}
catch (LdapException e)
{
Console.WriteLine(e.Message);
}
try
{
options.StopTransportLayerSecurity();
Console.WriteLine("Stop TLS succeeded\n");
}
catch (Exception e)
{
Console.WriteLine("Stop TLS failed with {0}", e.Message);
}
Console.WriteLine("Switching to negotiate auth type");
connection.AuthType = AuthType.Negotiate;
Console.WriteLine("\nRe-binding to the directory");
connection.Bind();
// complete some action over this non-SSL connection
// note, because Negotiate was used, the bind request
// is secure.
// run a task using this new binding
TestTask(hostName);

After a bit more work on this issue I found that I was running up against a couple of issues:
There was a bug in the code where the port number was being incorrectly changed to the SSL port (636) when connecting to AD in our test suite (doh!).
The OpenLDAP test server (that was a replica of our customers) was using openldap-2.4.18 - which has known issues with StartTLS.
After applying a patch to OpenLDAP (as discussed here - http://www.openldap.org/lists/openldap-bugs/200405/msg00096.html) we were able to fix #2 - at which point we started getting a different error "A local error occurred".
Though originally we had this code:
connection.SessionOptions.VerifyServerCertificate
+= (conn, cert) => {return true;};
We had removed it while testing, and because the OpenLDAP server was using a self-signed cert, that was not in a trusted store. Re-introducing that callback resolved this issue, though we now make it a configurable option i.e. "Verify Server Certificate Y/N" so customers need to opt into skipping the check (mostly for our QA team to use).
Thanks Steffen for pointing me in the direction of OpenLDAP versions which lead me to this solution.

Related

Connect to LDAP server and hit error in ASP.NET C# webform

I am using Windows authentication in a Webforms application, and I want to get the user's email address, but I think I hit the error when connecting to the server. Anything wrong with my code?
I had tried the strAccountId with/without domain name, (sAMAccountName=john).
The server is not operational.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Runtime.InteropServices.COMException: The server is not operational
Code:
string path = "LDAP://XYZ.LOCAL/CN=XYZ.LOCAL,OU=XXX,DC=XYZ,DC=LOCAL";
// The value of User.Identity.Name is XYZ\john
string strAccountId = "XYZ\\john";
string strPassword = "xxxxx";
bool bSucceeded;
string strError;
DirectoryEntry adsEntry = new DirectoryEntry(path, strAccountId, strPassword);
DirectorySearcher adsSearcher = new DirectorySearcher(adsEntry);
adsSearcher.Filter = "(sAMAccountName=" + strAccountId + ")";
try
{
SearchResult adsSearchResult = adsSearcher.FindOne();
bSucceeded = true;
strError = "User has been authenticated by Active Directory.";
EmailMsg.Text = strError;
adsEntry.Close();
}
catch (Exception ex)
{
bSucceeded = false;
strError = ex.Message;
EmailMsg.Text = strError;
adsEntry.Close();
}
In path you cannot put OUs, you need to do that after with adsEntry.Path.
string path = "LDAP://XYZ.LOCAL";
string strAccountId = "XYZ.LOCAL\\john";
string strPassword = "xxxxx";
DirectoryEntry adsEntry = new DirectoryEntry(path, strAccountId, strPassword);
adsEntry.Path = "LDAP://CN=XYZ.LOCAL,OU=XXX,DC=XYZ,DC=LOCAL";
Your path has three parts:
LDAP:// is the protocol
XYZ.LOCAL is the server to connect to. This is optional and can be excluded if the computer you run this from is joined to the same domain you're trying to connect to, or to a trusted domain.
CN=XYZ.LOCAL,OU=XXX,DC=XYZ,DC=LOCAL is the object on the domain to bind to. This is also optional. If excluded, it will bind to the root of the domain that the server in part 2 is part of. You must include either part 2 or 3, or both.
Since you have included the optional server name, it will try to connect to XYZ.LOCAL on the default LDAP port 389. "The server is not operational" means that it could not open a connection to XYZ.LOCAL on port 389. This is a network error and you need to figure out why the domain is not accessible from the computer you are running this from.
You can test the connection in PowerShell using:
Test-NetConnection XYZ.LOCAL -Port 389

how to send email using MailKit in Asp.Net Core

Am not sure whether the old method of sending Mail using Mailkit is quite working with this code below
try
{
var emailMessage = new MimeMessage();
emailMessage.From.Add(new MailboxAddress(_emailConfig.SenderName, _emailConfig.SenderAddress));
emailMessage.To.Add(new MailboxAddress(email));
emailMessage.Subject = subject;
var builder = new BodyBuilder
{
HtmlBody = message
};
emailMessage.Body = builder.ToMessageBody();
using var smtp = new SmtpClient
{
ServerCertificateValidationCallback = (s, c, h, e) => true
};
smtp.AuthenticationMechanisms.Remove("XOAUTH2");
await smtp.ConnectAsync(_emailConfig.SmtpServer, Convert.ToInt32(_emailConfig.Port), false).ConfigureAwait(false);
await smtp.AuthenticateAsync(_emailConfig.Username, _emailConfig.Password).ConfigureAwait(false);
await smtp.SendAsync(emailMessage).ConfigureAwait(false);
await smtp.DisconnectAsync(true).ConfigureAwait(false);
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.Message);
}
but am having exceptions if i use the code above to send email
nvalidOperationException: 534: 5.7.14 <https://accounts.google.com/signin/continue?sarp=1&scc=1&plt=AKgnsbv
5.7.14 26mzQKtlwfyEdGzHHdpi3ewWG6skAWgOBbdNNYmwzr9Sg3fGu-KixLAfODpJsVafutidE
5.7.14 8xBOp_8rNCvk9Y6iEcOkDlcZ1d-483zQ1Krw04NvqxQdq3w4iTtC8E9bL8uGprgV>
5.7.14 Please log in via your web browser and then try again.
5.7.14 Learn more at
5.7.14 https://support.google.com/mail/answer/78754 o5sm2555896wmh.8 - gsmtp
so when i changed this line of code below to use SSLS, A new error came out
await smtp.ConnectAsync(_emailConfig.SmtpServer, Convert.ToInt32(_emailConfig.Port), true).ConfigureAwait(false);
Exception returned
InvalidOperationException: An error occurred while attempting to establish an SSL or TLS connection.
This usually means that the SSL certificate presented by the server is not trusted by the system for one or more of
the following reasons:
1. The server is using a self-signed certificate which cannot be verified.
2. The local system is missing a Root or Intermediate certificate needed to verify the server's certificate.
3. A Certificate Authority CRL server for one or more of the certificates in the chain is temporarily unavailable.
4. The certificate presented by the server is expired or invalid.
Another possibility is that you are trying to connect to a port which does not support SSL/TLS.
It is also possible that the set of SSL/TLS protocols supported by the client and server do not match.
See https://github.com/jstedfast/MailKit/blob/master/FAQ.md#SslHandshakeException for possible solutions.
have searched everywhere on how to do it,even turned on my less secured app. some recommended sendGrid, i created a free account with them also,but i dont have access to the account created. Does anyone knows how to fix the code above using Mailkit
Try it like this.
using (var smtpClient = new SmtpClient())
{
smtpClient.ServerCertificateValidationCallback = (s, c, h, e) => true;
await smtpClient.ConnectAsync("host", port, false);
if (smtpClient.Capabilities.HasFlag(SmtpCapabilities.Authentication))
{
smtpClient.AuthenticationMechanisms.Remove("XOAUTH2");
await smtpClient.AuthenticateAsync(username, password);
}
await smtpClient.SendAsync(mailMsg);
smtpClient.Disconnect(true);
}

Connecting to LDAP with PHP Successfully, but cannot connect with C#

I am connecting successfully to the LDAP with PHP, tried a whole lot of things but when I try with C# am always getting either "Server is not operational" or "The LDAP server in unavailable".
Here is the PHP code:
<?php
function login($username='user', $password='pass') {
if (empty($username) || empty($password)) {
throw new BadCredentialsException('Invalid username or password.');
}
if (($ldap = #ldap_connect($url = 'ldap://ds.xyz-example.com', $port = 636)) === false) {
echo('Error connecting LDAP server with url %s on port %d.');
}
if (!#ldap_bind($ldap, sprintf($dn='uid=%s,ou=People,dc=xyz-example,dc=com', $username), $password)) {
$error = ldap_errno($ldap);
if ($error === 0x31) {
echo('Invalid username or password.');
} else {
echo('error during authentication with LDAP.');
}
}
return true;
}
login(); // call the function
?>
This is working perfect but I need it with C#. How can I do this with C# using the port and the dn and the user and pass?
Here is what I tried with C# but with an error "Server is not operational"
string ldapPath = "LDAP://ds.xyz-example.com:636/UID=user,OU=People,DC=xyz-example,DC=com";
string user = "user";
string password = "pass";
DirectoryEntry deSSL = new DirectoryEntry(ldapPath, user, password, AuthenticationTypes.SecureSocketsLayer);
try
{
user = deSSL.Properties["uid"][0].ToString(); //if this works, we bound successfully
Response.Output.Write("Success... {0} has bound", user);
}
catch (Exception ex)
{
Response.Output.Write("Bind Failure: {0}", ex.Message);
}
Thanks in advance!
Could it be your library doesn't implement LDAP but rather a weird non-standard Microsoft version of LDAP called ActiveDirectory, which only works when the server is an actual ActiveDirectory Server and doesn't quite work as easily when you use non-microsoft servers, such as OpenLDAP?
Could it be?

Can't connect to Gmail with IMAPX using c#

I'm following an example from IMAPX but it will not connect to GMAIL.
IMAP is enabled for the account and I've triple checked the username and password but it won't connect:
var server = ConfigurationManager.AppSettings["server"];
var login = Decrypt(ConfigurationManager.AppSettings["user"]);
var password = Decrypt(ConfigurationManager.AppSettings["pass"]);
//create the IMAP CLient
var client = new ImapClient(server, true);
//connect to the server
if (!client.Connect())
{
Console.WriteLine("Error: Failed to connect");
return;
}
//login to the server
if (!client.Login(login, password))
{
Console.WriteLine("Error: Invalid login");
return;
}
Anyone have any idea how to use this library to connect to gmail? I have tried variations of "use SSL" and "verify certificate", but no mater what I try the login always fails.
Google by default does not allow "less secure" apps from account access unless the account is setup to allow it.
References:
https://support.google.com/accounts/answer/6010255?hl=en
https://security.stackexchange.com/questions/66025/what-are-the-dangers-of-allowing-less-secure-apps-to-access-my-google-account

No message or timeout when trying to connect to sftp server

I'm trying to connect to a sftp server and I don't get any exception or timeout, only increasing memory usage.
I am using the library EnterpriseDT.Net.Ftp
My code is like this:
XmlConfigurator.Configure();
Module1.Logger = LogManager.GetLogger("SftpTester");
try
{
Module1.Logger.Info((object) "[EnterpriseDT] - Start");
Uri uri1 = new Uri("ftp://*****");
// ISSUE: explicit reference operation
// ISSUE: variable of a reference type
Uri& uri2 = #uri1;
int port = ****;
string str1 = "******";
// ISSUE: explicit reference operation
// ISSUE: variable of a reference type
string& UserName = #str1;
string str2 = "*******";
// ISSUE: explicit reference operation
// ISSUE: variable of a reference type
string& Password = #str2;
SecureFTPConnection secureFtpConnection = Module1.InitConnection(uri2, port, UserName, Password);
Module1.Logger.Info((object) "Connecting to ftp...");
secureFtpConnection.Connect();
Module1.Logger.Info((object) "Connection Successful!!!");
try
{
Module1.Logger.Info((object) "Disposing connection...");
secureFtpConnection.Dispose();
}
catch (Exception ex)
{
ProjectData.SetProjectError(ex);
ProjectData.ClearProjectError();
}
Module1.Logger.Info((object) "Connection Disposed.");
}
catch (Exception ex)
{
ProjectData.SetProjectError(ex);
Exception exception = ex;
Module1.Logger.Error((object) ("Main() - " + exception.Message));
Module1.Logger.Error((object) ("StackTrace: " + exception.StackTrace));
if (exception.InnerException != null)
Module1.Logger.Error((object) ("InnerException: " + exception.InnerException.Message));
ProjectData.ClearProjectError();
}
finally
{
Module1.Logger.Info((object) "[EnterpriseDT] - End");
}
private static SecureFTPConnection InitConnection(ref Uri uri, int port, ref string UserName = "", ref string Password = "")
{
Module1.Logger.Info((object) "InitConnection() - Setting Up Connection");
SecureFTPConnection secureFtpConnection = new SecureFTPConnection();
secureFtpConnection.LicenseOwner = "*******";
secureFtpConnection.LicenseKey = "***********";
secureFtpConnection.ServerAddress = uri.Host;
Module1.Logger.Info((object) ("\tHost: " + uri.Host));
secureFtpConnection.UserName = UserName;
Module1.Logger.Info((object) ("\tUsername: " + UserName));
secureFtpConnection.Protocol = FileTransferProtocol.SFTP;
Module1.Logger.Info((object) ("\tProtocol: " + FileTransferProtocol.SFTP.ToString()));
secureFtpConnection.ServerValidation = SecureFTPServerValidationType.None;
Module1.Logger.Info((object) ("\tServerValidation: " + SecureFTPServerValidationType.None.ToString()));
secureFtpConnection.AuthenticationMethod = AuthenticationType.Password;
Module1.Logger.Info((object) ("\tAuthenticationMethod: " + AuthenticationType.Password.ToString()));
if (port > 0)
{
secureFtpConnection.ServerPort = port;
Module1.Logger.Info((object) ("\tServerPort: " + port.ToString()));
}
secureFtpConnection.Password = Password;
Module1.Logger.Info((object) ("\tPassword: " + Password));
return secureFtpConnection;
}
Log message:
2014-09-27 04:50:22,783 [1] - [SftpTester] [EnterpriseDT] - Start
2014-09-27 04:50:22,799 [1] - [SftpTester] InitConnection() - Setting Up Connection
2014-09-27 04:50:22,971 [1] - [SftpTester] Host: *******
2014-09-27 04:50:22,971 [1] - [SftpTester] Username: *****
2014-09-27 04:50:22,971 [1] - [SftpTester] Protocol: SFTP
2014-09-27 04:50:22,971 [1] - [SftpTester] ServerValidation: None
2014-09-27 04:50:22,971 [1] - [SftpTester] AuthenticationMethod: Password
2014-09-27 04:50:22,971 [1] - [SftpTester] ServerPort: ****
2014-09-27 04:50:22,971 [1] - [SftpTester] Password: ******
2014-09-27 04:50:22,971 [1] - [SftpTester] Connecting to ftp...
Any idea if this is a timeout error or if I am in a blacklist?
Update 07/10/2014
Sequence for server:
Password authentication
Waiting for packet
Packet arrived
Auth partial success. Try: password, publickey, keyboard-interactive
Keyboard interactive authentication
Waiting for packet
Packet arrived
Prompt: Password:
Waiting for packet
Packet arrived
Auth partial success. Try: password, publickey, keyboard-interactive
Waiting for packet
Packet arrived
Prompt: Password:
Loop (9 to 15)
update
Updating the library solve the problem, was a bug.
Version 8.6.1
(23 Sep 2014)
Fixed kbi re-entrant bug that causes a loop of authentication attempts.
Fixed SFTP bug where an exception wasn't thrown when uploading a file to a non-existent directory.
Fixed SFTP problem where an OpenVMS SFTP server wasn't being recognized as SSH.
Fixed retry download problem where only one reconnect was made.
Fixed "Attempted to read or write protected memory" issue on some 2012 R2 machines using FTPS.
Your code looks ok. There is nothing special about it. The first thing you should do is enable Debug logging in the library, since you don't have enough information about the execution of your FTP code.
You can enable Debug with the following statement:
EnterpriseDT.Util.Debug.Logger.CurrentLevel = EnterpriseDT.Util.Debug.Level.DEBUG;
By default it will print debug info in the Console. you can use a custom appender (file, db etc.) if you like. The Debug info will give you much more information about the issue.
Any idea about if is a timeout error?
I don't think so. There is a client timeout set to 120000ms in the library by default. The Server timeouts are probably much higher. I don't think it is a timeout issue.
Am in a blacklist?
Maybe, but again that shouldn't hang an FTP client.
Why there is No message or timeout then?
Hangs could be caused by improper FTP Server or Firewall config. EnterpriseDT.Net.Ftp uses FTP Passive mode by default like most FTP clients. I would suggest checking your FTP server and firewall configuration and check everything is configured properly for Passive mode.
If your FTP Server is configured properly, you should be able to connect to it from any FTP client. You can try FileZilla or any other client, just to make sure your server is setup correctly. That way you will rule out any firewall or FTP server issues.
Update:
From what we see so far in the log it is an Auth issue. Looks like the SSH server supports password, publickey and keyboard-interactive auth.
My interpretation of your log above is that your SFTP client is trying to authenticate with the following methods in this exact order:
Try: password, publickey, keyboard-interactive
Password authentication (the default in your code) doesn't go through (wrong password?) and then the client switches to Keyboard authentication and that's why you are getting a password prompt in SSH, and the SFTP library is not able to handle that scenario, since you are not including a prompt response.
To fix that you should check out the SSHAuthPrompt class in the library, and include the prompt answer with the password in your code using the KBIPrompts property on the SecureFTPConnection class:
http://www.enterprisedt.com/products/edtftpnetpro/doc/manual/api/html/T_EnterpriseDT_Net_Ftp_Ssh_SSHAuthPrompt.htm
This class has two params in the constructor. The prompt should match what you are getting from KBI, and that should be 'Password:', the response value should be the password.
You should also try configuring the AuthenticationMode to KeyboardInteractive.

Categories