TCP Client/Server hosted in Kestrel - asp.net-core

I would like to create a simple TCP Server / TCP Client and use a Controller to interface with that classes, to then host the controller in a kestrel webserver.
I wanted to use SimpleSockets to create TCP Clients and the Server.
The creator of that library describes two ways of instantiating a TCP-Server either by providing an SSL-Certificate or by just creating a TcpListener without the need of an certificate.
It is described here
I want to use the option by providing an ssl-certificate but I cannot figure out how to provide the constructor with that certficate that is managed by the kestrel webserver, is there any way of injecting it (if that is the right way to do it) into the constructor of the TCP-Server?
If yes how would I inject the certificate?
In the code of the webserver I add the certificate like that:
builder.Services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.AllowedCertificateTypes = builder.Configuration.GetSection("Authentication").GetSection("CertificateAuthentication").GetValue<CertificateTypes>("AllowedCertificateTypes");
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
var validationService = context.HttpContext.RequestServices.GetService<CertificateValidationService>();
if (validationService is not null)
{
if (validationService.ValidateCertificate(context.ClientCertificate))
{
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer),
new Claim(ClaimTypes.Name, context.ClientCertificate.Subject, ClaimValueTypes.String, context.Options.ClaimsIssuer)
};
context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name));
context.Success();
return Task.CompletedTask;
}
}
context.Fail("Invalid client certificate");
return Task.CompletedTask;
}
};
})
.AddCertificateCache();
And that is the code of the constructor of my TCP-Server class where I want to inject the certificate into:
public TcpServer(X509Certificate2 certificate)
{
Listener = new SimpleSocketTcpSslListener(certificate);
}
Thanks in advance.

Related

SSL connectivity to Redis with StackExchange.Redis

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)
{
}

How to attach client certificate to AspNetCore TestServer api?

I'm looking at Microsoft.AspNetCore.TestHost.TestServer in the Microsoft source code. There I see the CreateClient() property that is HttpClient objects.
So how do I attached the client certificate for Digitial Signature in xUnit Test?
HttpClient example would be this.
var x509Certificate2 = new X509Certificate(); // Pretend it contains certificate data already.
var httpClientHandler = new HttpClientHandler() {
ClientCertificateOptions = ClientCertificateOption.Manual
};
httpClientHandler.ClientCertificates.Add(x509Certificate2);
using (var httpClient = new HttpClient(httpClientHandler))
{
}
Now using the TestServer
var testServer = new TestServer();
testServer.CreateClient(); // How do I attached client certificate here?
So, how do we attach the client certificate here? For CreateClient()
Also, I can try to make do with implementing the HttpRequestMessage but it doesn't support that certificate option either.
You may try to use the testServer.SendAsync(...) method and just construct your HttpContext from there.
var result = await server.SendAsync(context =>
{
context.Connection.ClientCertificate = myClientCertificate;
context.Request.Method = "POST";
});
Just make sure to specify the Path and the Body as needed.

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.

How might I get to the SSL URL defined in the properties of an MVC project?

I have SSL enabled and I am trying to write my own RequireHttpsAttribute to allow easy testing with IIS Express. When it redirects, it redirects to port 44301, but I don't want to hard code this, I want to read this from the existing configuration.
public sealed class RequireHttpsAlternativePortAttribute : RequireHttpsAttribute
{
protected override void HandleNonHttpsRequest(AuthorizationContext filterContext)
{
base.HandleNonHttpsRequest(filterContext);
//alter the port in the base classes result
var baseRedirect = filterContext.Result as RedirectResult;
if (baseRedirect == null)
{
throw new Exception("No redirect was suppied from the base class");
}
var builder = new UriBuilder(baseRedirect.Url);
var targetPort = 44301; //need this from settings
if (builder.Port == targetPort) return; //already correct port
//change the port
builder.Port = targetPort;
var redirectResult = new RedirectResult(builder.ToString());
filterContext.Result = redirectResult;
}
}
44301 is defined in the MVC project properties under SSL URL. My question is, is this accessible during runtime anywhere?

Dealing with Azure staging crazy URL

i'm deploying a webrole in azure that contains a web-site and a wcf service...
The site consumes services from the wcf.
The problem here is that the staging deploy creates a crazy url for the endpoints and i have to keep changing the endpoints in the web.config...
I'm wondering if theres a way to either "predict" what the url will be or to force one or even point to a generic host such as "localhost"???
You should be able to use role discovery to find the WCF endpoint. See this SO answer here and the blog post it links to.
My own abstract base class for connecting to azure services was based on that article. It uses role discovery to crate a channel like this:
#region Channel
protected String roleName;
protected String serviceName;
protected String endpointName;
protected String protocol = #"http";
protected EndpointAddress _endpointAddress;
protected BasicHttpBinding httpBinding;
protected NetTcpBinding tcpBinding;
protected IChannelFactory channelFactory;
protected T client;
protected virtual AddressHeader[] addressHeaders
{
get
{
return null;
}
}
protected virtual EndpointAddress endpointAddress
{
get
{
if (_endpointAddress == null)
{
var endpoints = RoleEnvironment.Roles[roleName].Instances.Select(i => i.InstanceEndpoints[endpointName]).ToArray();
var endpointIP = endpoints.FirstOrDefault().IPEndpoint;
if(addressHeaders != null)
{
_endpointAddress = new EndpointAddress(new Uri(String.Format("{1}://{0}/{2}", endpointIP, protocol, serviceName)), addressHeaders);
}
else
{
_endpointAddress = new EndpointAddress(String.Format("{1}://{0}/{2}", endpointIP, protocol, serviceName));
}
}
return _endpointAddress;
}
}
protected virtual Binding binding
{
get
{
switch (protocol)
{
case "tcp.ip":
if (tcpBinding == null) tcpBinding = new NetTcpBinding();
return tcpBinding;
default:
//http
if (httpBinding == null) httpBinding = new BasicHttpBinding();
return httpBinding;
}
}
}
public virtual T Client
{
get
{
if (this.client == null)
{
this.channelFactory = new ChannelFactory<T>(binding, endpointAddress);
this.client = ((ChannelFactory<T>)channelFactory).CreateChannel();
((IContextChannel)client).OperationTimeout = TimeSpan.FromMinutes(2);
var scope = new OperationContextScope(((IContextChannel)client));
addCustomMessageHeaders(scope);
}
return this.client;
}
}
#endregion
And in a derived class I pass it the following variables (for example):
this.roleName = "WebServiceRole";
this.endpointName = "HttpInternal";
this.serviceName = "services/Accounts.svc";
I never need to refer to the staging (or production) URLs at all.
See my answer here for more details: Add WCF reference within the same solution without adding a service reference
There is no way to either predict the GUID, control it, or use some constant name.
What you can do, to make things easier, is to move the URL into .CSCFG and update the URL of the WCF service from Azure Management Portal