SSL connectivity to Redis with StackExchange.Redis - ssl

I am having a very weird issue with StackExchange.Redis to connect with Redis.
I have enabled SSL on Redis database and I am not able to connect from client to Redis server with SSL certificate with below code.
static RedisConnectionFactory()
{
try
{
string connectionString = "rediscluster:13184";
var options = ConfigurationOptions.Parse(connectionString);
options.Password = "PASSWORD";
options.AllowAdmin = true;
options.AbortOnConnectFail = false;
options.Ssl = true;
options.SslHost = "HOSTNAME";
var certificate = GetCertificateFromThubprint();
options.CertificateSelection += delegate
{
return certificate;
};
Connection = new Lazy<ConnectionMultiplexer>(
() => ConnectionMultiplexer.Connect(options)
);
}
catch (Exception ex)
{
throw new Exception("Unable to connect to Cache Server " + ex);
}
}
public static ConnectionMultiplexer GetConnection() => Connection.Value;
public static IEnumerable<RedisKey> GetCacheKeys()
{
return GetConnection().GetServer("rediscluster", 13184).Keys();
}
// Find certificate based on Thumbprint
private static X509Certificate2 GetCertificateFromThubprint()
{
// Find certificate from "certificate store" based on thumbprint and return
StoreName CertStoreName = StoreName.Root;
string PFXThumbPrint = "NUMBER";
X509Store certLocalMachineStore = new X509Store(CertStoreName, StoreLocation.LocalMachine);
certLocalMachineStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certLocalMachineCollection = certLocalMachineStore.Certificates.Find(
X509FindType.FindByThumbprint, PFXThumbPrint, true);
certLocalMachineStore.Close();
return certLocalMachineCollection[0];
}
However, If I create a console application and connect to Redis with above code then I am able to connect, but If I used same code from my web application to connect to redis then I am not able to connect.
Not sure if I am missing something.
Also, I went through "mgravell" post
In that post he has configured "CertificateValidation" method, In my scenario I want Redis to validate SSL certificate. so I have not implementation validation. And implemented "CertificateSelection" method to provide client certificate.

You can try to validate the cert using CertificateValidation. I tried the following code and it worked for me:
options.CertificateValidation += ValidateServerCertificate;
...
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
return true;
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
return false;
}

In cases like this where you are using a client certificate and it works in a console app but does not work for some other application (you don't say but I guess from an IIS hosted web app), it almost always has to do with whether the account has permission to access the private key.
The console app runs with your account which probably has access to the private key.
To give an account access
open the Local Computer certificate store
find your client certificate
right click and choose "All tasks -> Manage Provate Keys..."
click "Add..." and add the account.
Note: if your adding an IIS App Pool account the format is:
IIS APPPOOL<my app pool name>
Location should be the local machine and not a domain.

I was able to ssl the Redis server I had started on a VM with the following codes.
add stackexchange.redis visual studio
try
{
ConfigurationOptions configurationOptions = new ConfigurationOptions
{
KeepAlive = 0,
AllowAdmin = true,
EndPoints = { { "SERVER IP ADDRESS", 6379 }, { "127.0.0.1", 6379 } },
ConnectTimeout = 5000,
ConnectRetry = 5,
SyncTimeout = 5000,
AbortOnConnectFail = false,
};
configurationOptions.CertificateSelection += delegate
{
var cert = new X509Certificate2("PFX FILE PATH", "");
return cert;
};
ConnectionMultiplexer connection =
ConnectionMultiplexer.Connect(configurationOptions);
IDatabase databaseCache = connection.GetDatabase();
//set value
databaseCache.StringSet("KEYNAME", "KEYVALUE");
//get Value
label_show_value.Text = databaseCache.StringGet("KEYNAME").ToString();
}
catch (Exception e1)
{
}

Related

MailKit 3.4.2 - "535: 5.7.3 Authentication unsuccessful"

I am trying to send mail to a client's Exchange server that uses NTLM authentication and am getting a "535: 5.7.3 Authentication unsuccessful" error when using port 587 and on port 465 I get this error "An error occurred while attempting to establish an SSL or TLS connection."
My development environment .Net 4.7.2
The code am using is below
// Add Content to Mime Message
var bodyBuider = new BodyBuilder();
message.Subject = subject;
bodyBuider.HtmlBody = body;
message.Body = bodyBuider.ToMessageBody();
#endregion
#region Send Mail
SmtpClient cli = new SmtpClient();
try
{
// mitigate signed exchange server certificate by an unknown certificate authority
cli.ServerCertificateValidationCallback = (s, c, h, e) => true;
cli.Connect(emailServerInfo.Host, emailServerInfo.Port, SecureSocketOptions.Auto);
// If username and Password is supplied
if (string.IsNullOrEmpty(emailServerInfo.UserName) == false)
{
if (cli.AuthenticationMechanisms.Contains("NTLM"))
{
var ntlm = new SaslMechanismNtlm(emailServerInfo.UserName, emailServerInfo.Password);
cli.Authenticate(ntlm);
}
else
{
cli.Authenticate(emailServerInfo.UserName, emailServerInfo.Password);
}
}
cli.Send(message);
}
catch (Exception ex)
{
this._audit.AddError(new Shared.Models.Audit.AuditErrorAddRequestInfo(ex));
ret.Add(ex.Message);
}
finally
{
cli.Disconnect(true);
}
#endregion
I think this code should work because I tried it with a Sendgrid and the email gets sent. I appreciate any help I can get
You probably need to disable SSL certificate revocation checks.
// mitigate signed exchange server certificate by an unknown certificate authority
cli.ServerCertificateValidationCallback = (s, c, h, e) => true;
cli.CheckCertificateRevocation = false;

What is the difference between public and private HTTPS connections?

I have a Net 5 Blazor server app published on a productive server and used a valid certificate for https requests, which works fine. Newly I added a HubConnection class to support SignalR notifications between web pages. But if I call the web page through an public URL like https://crm.example.com/call, I get the following error, although the same page works fine if I call it through an internal URL like https://10.12.0.151/call:
Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host..
I don't know why it happens, what is the difference between a public and private HTTPS connection? For public connection it uses a valid public certificate and actually works fine if I deactivate the SignalR notification.
Because of the application working just fine if I call the page with an internal URL, seems that all prerequisite for using SignalR are included such as installing WebSocket-Protocol feature on the server and so on.
The following snipet shows the part of code:
try
{
string sHubUrl = NavManager.BaseUri;
sHubUrl = sHubUrl.TrimEnd('/') + "/call";
LogBuilder.LogInfo($"URL in Call.NotificationInit: " + sHubUrl);
hubConnection = new HubConnectionBuilder()
.WithUrl(sHubUrl, options => {
options.UseDefaultCredentials = true;
options.HttpMessageHandlerFactory = (msg) =>
{
if (msg is HttpClientHandler clientHandler)
{
System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls12;
// bypass SSL certificate
clientHandler.ServerCertificateCustomValidationCallback +=
(sender, certificate, chain, sslPolicyErrors) => { return true; };
}
return msg;
};
})
.WithAutomaticReconnect()
.Build();
hubConnection.On<string, string>("NewMessage", ReceivedNotification);
await hubConnection.StartAsync();
}
catch (Exception ex)
{
LogBuilder.LogExecption("Exception at Call.NotificationInit");
}
What else should I do? Can anyone help me to solve this problem?

HttpClient POST request with Client Certificate

I'm trying to make a call to a third-party API which requires a client certificate. I generated the client certificate using the SSL tool and
uploaded this to the third party site. I have generated a successful POST request through Postman, providing the client certificate through
their dialogs.
The Headers are:
X-application = (MyApplicationName)
Content-Type = application/x-www-form-urlencoded
Accept = application/json
Body (x-www-form-urlencoded)
UserName = (username)
Password = (password)
When I perform a similar request through .NET I am receiving an error code indicating the certificate is not present. I have added the certificate to my personal certificate store and verified
the certificate has been added to the webhandler through debugging.
Can anyone suggest what the error might be or how I could diagnose the issue?
static async void LaunchRawHttpClient()
{
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12 | System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls;
ServicePointManager.ServerCertificateValidationCallback +=
ValidateServerCertificate;
string page = "https://<URL>";
var handler = new WebRequestHandler();
X509Certificate2 cert = GetMyCert();
if (cert!= null)
{
handler.ClientCertificates.Add(cert);
}
else
{
Console.WriteLine("Cert not found");
Console.ReadLine();
return;
}
// ... Use HttpClient.
using (HttpClient client = new HttpClient(handler))
{
client.DefaultRequestHeaders.Add("X-Application", "<applicationname>");
client.DefaultRequestHeaders.Add("Accept", "application/json");
var nvc = new List<KeyValuePair<string, string>>();
nvc.Add(new KeyValuePair<string, string>("username", "<username>"));
nvc.Add(new KeyValuePair<string, string>("password", "<password>"));
FormUrlEncodedContent reqContent = new FormUrlEncodedContent(nvc);
reqContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/x-www-form-urlencoded");
using (HttpResponseMessage response = await client.PostAsync(page, reqContent))
using (HttpContent content = response.Content)
{
// ... Read the string.
string result = await content.ReadAsStringAsync();
// ... Display the result.
if (result != null)
{
Console.WriteLine(result);
}
}
}
}
static X509Certificate2 GetMyCert()
{
string certThumbprint = "<thumbprint>";
X509Certificate2 cert = null;
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = store.Certificates.Find
(X509FindType.FindByThumbprint, certThumbprint, false);
if (certCollection.Count > 0)
cert = certCollection[0];
store.Close();
return cert;
}
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
Console.WriteLine("No SSL Errors");
return true;
}
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
Console.ReadLine();
return false;
}
I receive "No SSL Errors" message x2, followed by the missing certificate status code.
Thanks in advance
Jim
Finally found the answer on this - the problem was the private key file was not being loaded. Postman sent requests successfully, as did Curl. Curl asks for the key file explicity and this was a clue.
In .NET Core - there's a function on the X509Certificate2 object which allows you to copy it to another object combined with the key file. My project is in .NET framework and Core wasn't available.
The option I went for was using openssl to combine the cer and the key file into a pfx, which I loaded into the X509Certificate2 object. The Http Post then succeeded.

Elasticsearch NEST HttpClientHandler Certificate

I am trying to use Elasticsearch NEST with .NET Core and our Elasticsearch instance. We are connecting via SSL and it has a wildcard certificate which we need to accept programmatically. I am trying to figure out how to hook the HttpClientHandler to NEST to accept it. There doesn't appear to be good documentation on how, it just says to do it on their instructions https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/connecting.html#configuring-ssl.
I am looking for an example if possible. Thanks in advance!
I figured this out. I needed to create an HttpConnection and override the CreateHttpClientHandler method. Here is an example that returns true regardless of what the certificate is.
public class ConnectionWithCert : HttpConnection
{
protected override HttpClientHandler CreateHttpClientHandler(RequestData requestData)
{
var handler = base.CreateHttpClientHandler(requestData);
handler.ServerCertificateCustomValidationCallback = ValidateCertificate;
return handler;
}
private bool ValidateCertificate(HttpRequestMessage message, X509Certificate2 certificate, X509Chain chain, SslPolicyErrors errors)
{
return true;
}
}
A person would want to check the cert to ensure that it is one they expect.
Then, I added this connection in the ConnectionSettings
var connectionSettings = new ConnectionSettings(connnectionPool, new ConnectionWithCert());
Probably want to do some Dependency Injection, but figured I would share the solution just in case anyone else wonders what they need to do.
This took me some head scratching to figure out, so I thought I would post it here. We are using a reverse proxy where we send the request to 443 SSL port (load balanced in azure to three client nodes) using a cert to authenticate, then forward that to the local client node to scatter to the data nodes. The cert is self signed, and is in the local store (Current User > Personal) on the server housing our api. The thumbprint is in our web.config.
public class ConnectionWithCert : Elasticsearch.Net.HttpConnection
{
protected override HttpWebRequest CreateHttpWebRequest(RequestData requestData)
{
var handler = base.CreateHttpWebRequest(requestData);
string certThumbprint = System.Configuration.ConfigurationManager.AppSettings["ElasticsearchCertificateThumbprint"];
X509Certificate2 certificate =
GetCertificateByThumbprint(certThumbprint);
handler.ClientCertificates.Add(certificate);
return handler;
}
/// <summary>
/// Get the certificate using the certificate thumbprint
/// </summary>
/// <param name="certificateThumbprint">Thumbprint of certificate</param>
/// <returns>Certificate object</returns>
public static X509Certificate2 GetCertificateByThumbprint(string certificateThumbprint)
{
Ensure.ArgumentNotEmpty(certificateThumbprint, nameof(certificateThumbprint));
// Open the certificate store
X509Store certificateStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
certificateStore.Open(OpenFlags.ReadOnly);
// Get the certificates
var matchingCertificates = certificateStore.Certificates.Find(X509FindType.FindByThumbprint, certificateThumbprint, false);
if (matchingCertificates.Count == 0)
{
// No certificate found
return null;
}
else
{
// Return first certificate
return matchingCertificates[0];
}
}
}
Once I have this, I can set that on my connectionSettings in my helper class:
public ElasticSearchHelper(string elasticSearchUrl, OcvElasticSearchDataProvider dataProvider, int elasticSearchConflictRetryCount)
{
// Parameters
this.elasticSearchConflictRetryCount = elasticSearchConflictRetryCount;
this.dataProvider = dataProvider;
// Create the ElasticSearch client and configure
var node = new Uri(elasticSearchUrl);
var pool = new SingleNodeConnectionPool(node);
var settings = new ConnectionSettings(pool, new ConnectionWithCert());
this.client = new ElasticClient(settings);
}
Now all operations carried out through my helper have the client cert attached, and is granted access through my reverse proxy.

Not able to download file from Dropbox because failing to connect with SSL/TLS channel through WebClient in C#

Trying to download an executable file from Dropbox's private folder to PC in a Windows Service using WebClient.DownloadFile(). But it is throwing error
The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.
What weired is, error occurring only in Windows XP (SP2) and not in Windows 7, 8 & 8.1. (Not tested in XP SP3 and Vista yet.)
Tried with:
WebClient.UseDefaultCredentials is true.
WebClient.Credentials = CredentialCache.DefaultNetworkCredentials or CredentialCache.DefaultCredentials.
http:// in URL instead of https://.
Well, I solved it myself. Answer from this StackOverflow question, which has more votes, helped me to solve it.
My sample as follows:
public static void DownloadFileFromDropbox(string dropboxUrl, string file_name)
WebClient webclient = null;
try
{
webclient = new WebClient();
ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(ValidRemoteCertificate);
if (File.Exists(UpdatesDirStr + file_name))
{
File.Delete(UpdatesDirStr + file_name);
}
webclient.DownloadFile(dropboxUrl, UpdatesDirStr + file_name);
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (webclient != null)
{
webclient.Dispose();
webclient = null;
}
}
}
private static bool ValidRemoteCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (certificate.Subject.Contains("dropboxusercontent.com"))
{
return true;
}
else if (certificate.Subject.Contains("dropbox.com"))
{
return true;
}
return false;
}
I still wonder how does it work properly? Because new RemoteCertificateValidationCallback(ValidRemoteCertificate) is not taking dropboxUrl from anywhere. So how X509Certificate certificate param in ValidRemoteCertificate method got the correct certificate from Dropbox.com?