SignalR how to get client certificate when he connect to hub - c#

How can i get client certificate when he connect to the signalr Hub? My code look like this i read certificate from file and then trying to connect to the hub. I want to create certifate object on hub and read some information from it.
Hub code:
public class ServerHub:Hub
{
public override Task OnConnectedAsync()
{
string connectionId = Context.ConnectionId;
//get certificate ? `X509Certificate2 cert = new X509Certificate2(Certificate);`
return base.OnConnectedAsync();
}
Client code
string Certificate = #"C:\Users\StażCRM\Downloads\sample.cer";
X509Certificate2 cert = new X509Certificate2(Certificate);
string resultsTrue = cert.ToString(true);
// Display the value to the console.
Console.WriteLine(resultsTrue);
// Get the value.
string resultsFalse = cert.ToString(false);
// Display the value to the console.
Console.WriteLine(resultsFalse);
this.id = id;
HubConnection con = new HubConnectionBuilder().WithUrl("https://localhost:44375/ClinicServer",opt=>opt.ClientCertificates.Add(cert)).Build();
con.StartAsync().Wait();

According to this answer, you can get the certificate directly from the HttpContext.
SignalR with Client Certificate Authentication
However, the code is for .NET framework.
When using asp.net core I think you can get the certificate like this
this.Context.GetHttpContext().Connection.ClientCertificate

Related

gRPC and ASP Net Core: using SslCredentials with non-null arguments is not supported by GrpcChannel

I am trying to connect from a client to the service. The service is configurated to use a self signed Ssl certificate and I am trying to configurate the client with the client certificate. I am using this code:
string cacert = System.IO.File.ReadAllText("certificados/ca.crt");
string cert = System.IO.File.ReadAllText("certificados/client.crt");
string key = System.IO.File.ReadAllText("certificados/client.key");
KeyCertificatePair keypair = new KeyCertificatePair(cert, key);
SslCredentials sslCreds = new SslCredentials(cacert, keypair);
var channel = GrpcChannel.ForAddress("https://x.x.x.x:5001", new GrpcChannelOptions { Credentials = sslCreds });
var client = new Gestor.GestorClient(channel);
But I am getting the following error: using SslCredentials with non-null arguments is not supported by GrpcChannel.
I don't understand very good the message error. SslCredentials is ChannelCredentials? type, and SslCreds is Grpc.Core.SslCredentials. It can be compiled, so the type I guess it is correct.
What I would like to know it is how I can configure the client to use the self signed certificate that I have created.
Thanks.
The SslCredentials support in only available grpc-dotnet is to provide some level of compatibility with Grpc.Core in the most common use case, it doesn't expose all the functionality though. In grpc-dotnet, only SslCredentials() (parameterless which uses the default roots) is supported. If you want to provide your self-signed creds, you can certainly do that, you'll need to use a different API for configuring GrpcChannel:
See example here (creating a GrpcChannel with custom credentials).
https://github.com/grpc/grpc-dotnet/blob/dd72d6a38ab2984fd224aa8ed53686dc0153b9da/testassets/InteropTestsClient/InteropClient.cs#L170
I spend a fair bit of time googling around for solutions to this problem, and didn't find a concise answer. Here is ultimately how I was able to configure a dotnet client to use mutual SSL authentication:
MyService.MyServiceClient GetClient(){
var httpClientHandler = new HttpClientHandler();
// Validate the server certificate with the root CA
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, _) => {
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
chain.ChainPolicy.CustomTrustStore.Add(new X509Certificate2("ca.crt"));
return chain.Build(cert);
};
// Pass the client certificate so the server can authenticate the client
var clientCert = X509Certificate2.CreateFromPemFile("client.crt", "client.key");
httpClientHandler.ClientCertificates.Add(clientCert);
// Create a GRPC Channel
var httpClient = new HttpClient(httpClientHandler);
var channel = GrpcChannel.ForAddress("https://localhost:8080", new GrpcChannelOptions{
HttpClient = httpClient,
});
return new MyService.MyServiceClient(channel);
}

Custom ServiceStack OAuth2 provider

We are trying to communicate with a REST server, which uses its own OAuth2 implementation.
This server is written by another company in Java, so I don't have much influence about it.
I've got all the necessary information, like Access Token URL, Refresh URL, Client Id, Client Secret, etc. I can already request an access token and then request some other data from this server, using the REST client Postman.
Now I'd like to use the ServiceStack client (version 4.5.14), to communicate with this server in C# .NET 4.6.2.
My problem is: All the examples I found, e.g. http://docs.servicestack.net/authentication-and-authorization#custom-authentication-and-authorization are either about the server-side or about authentication against Facebook or Google.
I already implemented my own CustomOAuth2Provider, setting the access token URL, ConsumerSecret, etc.
But how do I tell the JsonServiceClient, to use this Provider, before executing the specific request?
Thank you,
Daniel
Edit:
I read a lot of documentation and ServiceStack sourcecode, and I think my main problems are the following:
I abuse the ServiceStack Client to communicate with a non-ServiceStack application, which I can not modify.
Maybe the OAuth2 implementation of the third-party application is not 100% correct, as it expects authorization and token request in the same request.
But I got it working now and would like to show my solution here.
It still can be improved, e.g. it does not use the received refresh token right now.
public class ThirdPartyAuthenticator : IDisposable
{
// TODO: Move to config
public const string AccessTokenUrl = "";
public const string ConsumerKey = "";
public const string ConsumerSecret = "";
public const string Username = "";
public const string Password = "";
/// <summary>
/// Remember the last response, instance comprehensive so we do not need a new token for every request
/// </summary>
public static ServiceModel.ThirdPartyOAuth2Response LastOAuthResponse = null;
/// <summary>
/// This already authenticated client can be used for the data requests.
/// </summary>
public JsonServiceClient AuthenticatedServiceClient { get; set; }
public ThirdPartyAuthenticator()
{
if (LastOAuthResponse == null || (LastOAuthResponse.ExpiryDateTime < DateTime.Now)) // TODO: Use Refresh token?
{
// Get token first
JsonServiceClient authClient = new JsonServiceClient(AccessTokenUrl);
authClient.UserName = ConsumerKey;
authClient.Password = ConsumerSecret;
authClient.AlwaysSendBasicAuthHeader = true;
var request = new ServiceModel.ThirdPartyOAuth2Request();
request.Username = Username;
request.Password = Password;
// Use the Get URI, because server expects username + password as query parameter
LastOAuthResponse = authClient.Post<ServiceModel.ThirdPartyOAuth2Response>(request.ToGetUrl(), request);
}
// If no exception was thrown, we have a valid token here.
AuthenticatedServiceClient = new JsonServiceClient(AccessTokenUrl);
AuthenticatedServiceClient.BearerToken = LastOAuthResponse.AccessToken;
}
public void Dispose()
{
AuthenticatedServiceClient?.Dispose();
}
}
usage:
using (var foo = new ThirdPartyAuthenticator())
{
var response = foo.AuthenticatedServiceClient.Get(new ServiceModel.GetMyData() { SomeId = 10 });
}
OAuth providers require a browser to redirect to the OAuth provider site where Users are able to accept authentication with the App and any permissions it requires. Once the user accepts they're redirected back to your ServiceStack App where it will create an Authenticated User Session. The session id from the Authenticated User Session is what's configured on the ServiceStack client to establish authenticated requests.
Here are some Example Apps which use OAuth to Authenticate using a browser then capture the browser redirect to extract the session cookies and configure it on the C# Service Client where they're then able to make Authenticated requests:
https://github.com/ServiceStackApps/TechStacksAuth
https://github.com/ServiceStackApps/AndroidJavaChat

EWS exception : "Connection failed. Try later."

Every time I try to call the server, I get an error code : ErrorConnectionFailed with Connection failed. Try later. message.
I suspect that it comes from the fact that the credentials of service are empty. Although I have no idea why. If I create the credentials manually using my windows account login and password, it works fine : new WebCredentials(login, password, domain);
I have a console program that works fine (see below), but it does not on a web site.
static void Main(string[] args)
{
var service = GetContextualService(email);
EmailMessage email = EmailMessage.Bind(service, new ItemId(validEmailId));
Console.ReadKey();
}
private static ExchangeService GetContextualService(string email)
{
ExchangeService service = new ExchangeService();
// I don't even need credentials on a Console program to make it work
//service.Credentials = new WebCredentials(CredentialCache.DefaultCredentials);
//service.UseDefaultCredentials = true;
service.AutodiscoverUrl(email, RedirectionUrlValidationCallback);
return service;
}
private static bool RedirectionUrlValidationCallback(string redirectionUrl)
{
// The default for the validation callback is to reject the URL.
bool result = false;
Uri redirectionUri = new Uri(redirectionUrl);
// Validate the contents of the redirection URL. In this simple validation
// callback, the redirection URL is considered valid if it is using HTTPS
// to encrypt the authentication credentials.
if (redirectionUri.Scheme == "https")
{
result = true;
}
return result;
}
While using on a website even with new WebCredentials(CredentialCache.DefaultCredentials);, it returns an exception. (see below)
private ExchangeService GetContextualService(string email)
{
ExchangeService service = new ExchangeService();
service.Credentials = new WebCredentials(CredentialCache.DefaultCredentials);
//service.UseDefaultCredentials = true;
service.AutodiscoverUrl(email, RedirectionUrlValidationCallback);
return service;
}
[HttpPost]
public List<InternetMessageHeader> GetMailHeader(JObject data)
{
ExchangeService service = GetContextualService(data.GetValue("email").Value<string>());
ItemId id = new ItemId(data.GetValue("mailId").Value<string>());
// EXCEPTION BELOW
EmailMessage email = EmailMessage.Bind(service, id);
return email.InternetMessageHeaders.ToList();
}
Why does any call to EWS returns me an exception ?
Why is it working fine on a console program and not on a web server ?
Any thought is welcome !
Strictly based on the code you posted, all I can say is that when you call AutoDiscoverUrl() it may not be talking to the same server that you need to talk to with EWS. Altho typically AD and EWS are on the CAS, it's possible (I think) to get an EWS URL that points to some other server. I've not been in the code in the EWS Editor in some time, but if it does not call the Managed API, it might do AD slightly differently. I'd suggest calling out the EWS URL before you try the Bind() and seeing if you can paste it into a browser. (It'll redirect you OWA, but the connection will be proven.) I'd also call out the AD URL in the redirection callback so you know who you're talking to. Sorry I can't be of more help.

Programmatically created web service fails on client service call

I made a console application project to host a web service programmatically, but when I try to create a client proxy to my web service and call a method on it, I get the following error:
An error occurred while making the HTTP request to
https://localhost:8000/FileRetrievalPoC. 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.
Its inner exception:
The underlying connection was closed: An unexpected error occurred on
a send.
Its inner exception:
Unable to read data from the transport connection: An existing
connection was forcibly closed by the remote host.
Its inner exception:
An existing connection was forcibly closed by the remote host
Program.cs:
class Program
{
static void Main(string[] args)
{
var address = "https://localhost:8000/FileRetrievalPoC";
Console.WriteLine("Starting a service at {0}...", address);
FileRetrievalService.Start(address, StoreLocation.LocalMachine, StoreName.My, "localhost");
Console.WriteLine("Service started.");
Console.WriteLine("Press Enter to create a new proxy client and call the Get method.");
Console.WriteLine("Press Escape to end the application.");
while (true)
{
var key = Console.ReadKey();
if (key.Key == ConsoleKey.Enter)
{
var proxy = FileRetrievalService.Connect(address, "localhost", "exampleUsername", "examplePassword", StoreLocation.LocalMachine, StoreName.My, "localhost");
proxy.Get(#"C:\Users\User\Desktop\Document.txt");
((IClientChannel)proxy).Close();
}
else if (key.Key == ConsoleKey.Escape)
break;
}
FileRetrievalService.Stop();
}
}
IFileRetrieval.cs:
[ServiceContract]
public interface IFileRetrieval
{
[OperationContract]
string Get(string path);
[OperationContract]
void Set(string path, string contents);
}
FileRetrievalService.cs:
class FileRetrievalService : IFileRetrieval
{
private static BasicHttpsBinding _binding = new BasicHttpsBinding()
{
Name = "FileRetrievalPoC",
HostNameComparisonMode = HostNameComparisonMode.Exact,
Security = new BasicHttpsSecurity()
{
Message = new BasicHttpMessageSecurity()
{
AlgorithmSuite = SecurityAlgorithmSuite.Basic256Sha256Rsa15,
ClientCredentialType = BasicHttpMessageCredentialType.UserName
},
Mode = BasicHttpsSecurityMode.TransportWithMessageCredential,
Transport = new HttpTransportSecurity()
{
ClientCredentialType = HttpClientCredentialType.Windows
}
},
SendTimeout = TimeSpan.FromMinutes(1),
CloseTimeout = TimeSpan.FromMinutes(1),
OpenTimeout = TimeSpan.FromMinutes(1),
ReceiveTimeout = TimeSpan.FromMinutes(1)
};
private static ChannelFactory<IFileRetrieval> _channelFactory;
private static ServiceHost _host;
public static void Start(string address, StoreLocation location, StoreName name, string subject)
{
_host = new ServiceHost(typeof(FileRetrievalService));
_host.Credentials.ServiceCertificate.SetCertificate(location, name, X509FindType.FindBySubjectName, subject);
_host.AddServiceEndpoint(typeof(IFileRetrieval), _binding, address);
_host.Open();
}
public static void Stop()
{
if (_host != null)
_host.Close();
if (_channelFactory != null)
_channelFactory.Close();
}
public static IFileRetrieval Connect(string address, string domain, string username, string password, StoreLocation location, StoreName name, string subject)
{
if (_channelFactory == null)
_channelFactory = new ChannelFactory<IFileRetrieval>(_binding, address);
_channelFactory.Credentials.ClientCertificate.SetCertificate(location, name, X509FindType.FindBySubjectName, subject);
_channelFactory.Credentials.UserName.UserName = username;
_channelFactory.Credentials.UserName.Password = password;
_channelFactory.Credentials.Windows.ClientCredential = new NetworkCredential(username, password, domain);
return _channelFactory.CreateChannel();
}
public string Get(string path)
{
throw new NotImplementedException();
}
public void Set(string path, string contents)
{
throw new NotImplementedException();
}
}
Its all done programmatically, and I've looked on Stack Overflow but couldn't find a good reason why this is happening. Does anyone know what the problem is? This source code, you can add to a new console application and run it to try it out on your local machine and see it happen for yourself. Is it the SSL certificate? If so, how can I get more verbosity for the error reason here? Its not a very helpful exception.
Edit: I think I may have missed a step here, such as using netsh to bind a certificate to my machine's port.
My issue was that I did not use netsh to bind the certificate to my machine's port. Open up an administrative command prompt and call:
netsh http add sslcert ipport=0.0.0.0:8000 appid=<A randomly generated GUID for your application> certhash=<Your localhost certificate's thumbprint from the default MY store, which is under Local Machine -> Personal, which you can get from the MMC Certificates snap-in>
The next step is to make sure its under Trusted People on the client side. At least, for me this is the case since I am using a self-signed certificate that I generated for testing purposes for localhost. So for example, if you get a certificate from Comodo or Verisign or some other CA, your certificate may not need this at all since the root CA will be trusted, usually, by default in Windows, since the root CA public certificate for these is shipped out of the box inside of the Trusted Root Certification Authorities section of the Certificates MMC snap-in.
Then, all you need to do, is make sure that your machine credentials are correct. I am using Windows authentication so it tries to assert that my credentials are valid (these are specified in my code on the call to the Connect method).
As I get older, I find I tend to answer my own questions more and more often...
Edit: You only need to use the Trusted People store for all of this. If you do want to do this, then use StoreName.TrustedPeople in my code above and in your netsh command, specify certstorename=TrustedPeople, otherwise it defaults to MY, which is the Personal store in the Certificates MMC snap-in.
Also, to delete an SSL certificate that has been bound, use netsh http delete sslcert ipport=0.0.0.0:8000
Also, my code doesn't need the client certificate to be set in order to function, so that can be removed from the Connect method. Also needs some more tightening up if any of you plan to use it in production.

403 Error on Azure API Only After Deploying to IIS

I think I have read every single thing on the internet about this (bold statement I know) but I can't work it out...
I have a very simple webpage that gets the status VMs on Azure, which works fine on my machine. I created a Cert on my local machine with makecert and debug runs fine.
After deploying it to another server on IIS all I get is 403 errors.
Things I tried:
Exporting Cert from my dev machine with private key and importing onto the test server
Creating new Cert with makecert (edit: recreated the cert on the server I want to deploy to) (according to this link from MSN), upload to Azure, update code to search for new thumbprint, redeploy and admire the same error msg..
Both times I changed the app pool identity to a user account that is log-on-able (and reverted)
Tried with cert as both localmachine and current user, with user updated in the app pool
I changed my get cert code to more resemble an answer from a similar question, but finding the cert doesn't appear to be the issue.. if I remove the cert created on the server, I get a different error.
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
var certificate = store.Certificates.Cast<X509Certificate2>().SingleOrDefault(c => string.Equals(c.Thumbprint, thumbprint, StringComparison.OrdinalIgnoreCase)); // please replace CertificateThumbprint with original Thumbprint
return certificate;
Ref: how to connect to azure (management) rest api via C# in IIS
Code to create HttpClient:
WebRequestHandler handler = new WebRequestHandler();
String CertThumbprint = _certthumbprint;
X509Certificate2 managementCert = FindX509Certificate(CertThumbprint);
if (managementCert != null)
{
handler.ClientCertificates.Add(managementCert);
HttpClient httpClient = new HttpClient(handler);
httpClient.DefaultRequestHeaders.Add("x-ms-version", "2014-05-01");
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
return httpClient;
}
Retrieve VMs Code:
String uri = String.Format("https://management.core.windows.net/{0}/services/hostedservices/{1}/deploymentslots/{2}", _subscriptionid, ServiceName, "Production");
XDocument vms = new XDocument();
vms.Add(new XElement("VirtualMachines"));
ApplyNamespace(vms.Root, ns);
try
{
HttpClient http = GetHttpClient();
Stream responseStream = await http.GetStreamAsync(uri);
if (responseStream != null)
{
XDocument xml = XDocument.Load(responseStream);
var roles = xml.Root.Descendants(ns + "RoleInstance");
foreach (XElement r in roles)
{
XElement svcNamee1 = new XElement("ServiceName", ServiceName);
ApplyNamespace(svcNamee1, ns);
r.Add(svcNamee1);
vms.Root.Add(r);
}
}
}
This code is currently about 95% copy and paste from here
The resolution for me in this case was to create a new Publishsettings file via powershell and import that on the server via powershell. Then use the thumbprint from that in code. Making a cert on the server and uploading to Azure still doesn't work for whatever reason...

Categories