Request.HttpContext.Connection.ClientCertificate is always null - c#

I have an ASP.Net core website deployed on Azure app service for Linux.
In the controller, I am trying to get the client certificate like below:
var callerCertificate = Request.HttpContext.Connection.ClientCertificate;
I always get callerCertificate as null.
I have tried await Request.HttpContext.Connection.GetClientCertificateAsync() with same result null.
My website webhost creation looks like below:
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseStartup<Startup>()
.UseSerilog();
I have also set SSL setting for the website (in Azure) as below:
The client side caller is a net462 project that uses Microsoft.Rest.CertificateCredentials to set the certificate to HTTP request.
var cred = new CertificateCredentials(_deviceCertificate)
...
await this.cred.ProcessHttpRequestAsync(_httpRequest, cancellationToken).ConfigureAwait(false);

You could try to add the certificate using HttpClient directly instead of using Microsoft.Rest.CertificateCredential.
var clientHandler = new HttpClientHandler();
clientHandler.ClientCertificateOptions = ClientCertificateOption.Manual;
clientHandler.ClientCertificates.Add(_deviceCertificate);
var client = new HttpClient(clientHandler);
var result = client.GetAsync("https://yourservice").GetAwaiter().GetResult();
You may also need to configure the SSL protocol (SSL2, SSL3, TLS, etc.):
clientHandler.SslProtocols = SslProtocols.Tls;

Answering my own question:
I am able to get the client certificate from header
string clientCertFromHeader = Request.Headers["X-ARR-ClientCert"];
Though, it is still a mystery as to why Request.HttpContext.Connection.ClientCertificate is not giving the certificate.

Related

gRPC Server with SSL on .NET6 and OpenSSL

gRPC server part in .NET 6 also looks different, now there is no Startup.cs, only Program.cs and all the examples I found go through creating a new instance of the SERVER class. But what should it look like if I use .NET 6 (Kestrel)?
This is "server" default code with one sevice (MyTestService) of .NET 6 Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.MapGrpcService<MyTestService>();
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
This is the solution for the client from the official gRPC documentation:
var channelCredentials = new SslCredentials(File.ReadAllText("roots.pem")); // Load a custom
roots file.
var channel = new Channel("myservice.example.com", channelCredentials);
var client = new Greeter.GreeterClient(channel);
But there is no server solution.
UPDATED - CODE FOR CLIENT for gRPC .NET6:
string certificatePem = File.ReadAllText("clientcrt.pem");
string privateKeyPem = File.ReadAllText("clientkey.pem");
var cert = X509Certificate2.CreateFromPem(certificatePem,
privateKeyPem);
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(cert);
using HttpClient httpClient = new(handler);
var channel = GrpcChannel.ForAddress("https://0.0.0.0:5000", new GrpcChannelOptions
{
HttpClient = httpClient
});
var grpc = new Test.TestClient(channel);
You can still configure the Kestrel like you used to do with the new "simplified" .NET 6 layout, like it is explained in the MS Docs here. So, for the server-side Program.cs you posted, you can just configure the builder to use TLS. For example, if you have a certificate "server_certificate.pfx" for the server in the default code you posted, configure the builder like this:
// the code you posted, but with Kestrel configuration
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();
// configure the builder to use the TLS certificate
builder.WebHost.ConfigureKestrel(opt =>
{
string file = "server_certificate.pfx";
string password = "P#ssw0rd!";
var cert = new X509Certificate2(file, password);
opt.ConfigureHttpsDefaults(h => {
// Choose RequireCertificate instead of AllowCertificate if it is required
h.ClientCertificateMode = Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode.AllowCertificate;
// this checks whether the certificate has been signed by some greater authority
h.CheckCertificateRevocation = false;
h.ServerCertificate = cert;
});
});
var app = builder.Build();
// Configure the HTTP request pipeline.
app.MapGrpcService<MyTestService>();
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
Another option is to just use the old .NET 5 programming style for the Program.cs in .NET 6 (which I prefer), like it is described here in the MS Docs for console apps. An example for how this could look like is this Program.cs file, which can be found in the repo that #Mihal By linked in the comments. If you go back to the old style, you can also just write your own Startup.cs for the Kestrel like you used to.

RestSharp isn't consistent when calling wordpress/woocomerce site

In my dev environment, I have a wordpress/woocommerce site locally all hosted under IIS.
Using the following code restsharp works:
var fullEndPoint = string.Concat(site, "/wp-json/wc/v3/", endpoint)
var client = new RestSharp.RestClient(fullEndPoint)
{
Authenticator = OAuth1Authenticator.ForProtectedResource(consumerkey, woosecret, "", "")
};
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/json");
request.AddJsonBody(serializedData);
client.Execute(request);
When I change the fullEndPoint, consumerkey and woosecret to be our main test site which mirrors our live site (running on apache/linux), I receive
Sorry you are not allowed to create Resource.
If I post to our test site the same data using postman, it goes through and works.
For the purposes of testing, if I then change the above declaration of client to be:
var client = new RestClient("https://wordpress.mytestsite.com/wp-json/wc/v3/products?oauth_consumer_key=ck_a24ad9ddea2d9fe71d9172c415fd51b4dc83a6dc&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1627038994&oauth_nonce=T9gfeZGdmNx&oauth_version=1.0&oauth_signature=OJB1TBpLpA%2Bet0A%2FDFbozOT9nf8%3D");
where fullendpoint is the code shown in postman's code windows for Restsharp, again the code works, so I know the error is misleading. So Why doesn't restsharp work in the initial way for me when I point to my test site?
Edit:
If I change client such that I'm now setting it up as:
var client = new RestSharp.RestClient(fullEndPoint)
{
Authenticator = OAuth1Authenticator.ForClientAuthentication(consumerkey, woosecret, username, password)
};
where username and password equal the user set up in woo commerce against the consumer key for read/write access, I still receive an access denied error

HttpClient certificate when hosted in IIS/Asp.NET Application not work

I have a strange problem.
I need to perform an SSL request using a CER client certificate, to a server that requires authentication by that certificate.
I am using the code below:
var cert = X509Certificate.CreateFromCertFile("cert.cer");
var handler = new WebRequestHandler();
handler.ClientCertificates.Add(cert);
var http_client = new HttpClient(handler);
http_client.BaseAddress = new Uri("https://service.com/");
var str_json = JsonConvert.SerializeObject(new
{
Field = "Value1",
Fiesl2 = "Value2"
});
var byteContent = new ByteArrayContent(Encoding.UTF8.GetBytes(str_json));
byteContent.Headers.Remove("Content-Type");
byteContent.Headers.Add("Content-Type", "application/json");
var res = http_client.PostAsync("ResourcePath", byteContent).Result;
res.EnsureSuccessStatusCode(); //THe error 401 ocurrs here
var res_body = res.Content.ReadAsStringAsync().Result;
This code works perfectly when I squeeze into a ConsoleApplicaiton or a WebApplication in IIS Express.
But when I squeeze exactly the same code in Local IIS or IIS Server, I get the 401-Unauthorized error. The strange thing is that using Fiddler, in this case I can not even see the request attempt.
I've already checked that path is not the problem.
The problem occours in .NET 4.5, 4.5.1, 4.5.2, 4.6, 4.6.1 and etc..
Can anyone help me out, is it any configuration that should be performed in IIS. I've researched a lot, but I did not find that specific error.
A .cer file at client side does not contain private key, so usually it won't work if mutual SSL/TLS is required by the server. You need to get a valid certificate with private key (usually a .pfx file).

Using Default Credentials in System.Net.HttpClient request in UWP

We are porting our Windows 8.1 app to UWP and are experiencing an issue with sending default user credentials for single sign-on. It seems that credentials are not being set, despite setting UseDefaultCredentials = true on the handler. This exact code was working in Windows 8.1.
using (var client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true }))
{
// client._handle.Credentials is null
// This call fails authentication with a 401
// and Fiddler shows no attempt to pass authentication
var response = await client.GetStringAsync(new Uri(GlobalConfig.Config.BaseUri, "Presenter/SingleSignOn"));
...
}
As noted above, in debugging I can see that client._handle.Credentials is null.
I have also tried setting credentials from the System.Net.CredentialCache, but these appear to return empty credentials and they also result in a null client._handle.Credentials.
var handler = new HttpClientHandler() { Credentials = System.Net.CredentialCache.DefaultCredentials };
var handler = new HttpClientHandler() { Credentials = System.Net.CredentialCache.DefaultNetworkCredentials };
I have double-checked our declared Capabilities as per this answer and they look fine. We have declared capabilities:
Enterprise Authentication
Internet (client)
Private Networks (Client & Server)
Removable Storage
I have tried using Windows.Web.HttpClient, but it has the same issue--it doesn't find default credentials, so it prompts via a UI. Am I missing something obvious? Or something non-obvious?
TL;DR - I am having trouble passing default user credentials in HttpClient requests.
Edit: I should add that authentication in general is working. If I pass a username/password explicitly, then it works. Obviously, that's not the goal here.

UWP app HttpClient HTTPS client certificate problems

I'm writing a UWP app in C# that is eventually destined for IoT, but right now I've only been debugging locally. I'm using Windows.Web.Http.HttpClient to connect to a self-hosted WCF REST web service that I've also written and have running as a Console app on the same machine for testing. The service requires mutual authentication with certificates, so I have a CA cert, service cert, and client cert.
My UWP code works like this:
Check app cert store for client cert and CA cert installed.
If not, install from PFX file and CER file, respectively.
Attach the Certificate to the HttpBaseProtocolFilter and add the filter to the HttpClient
Call the HttpClient.PostAsync
After I call PostAsync I get the following error: An Error Occurred in the Secure Channel Support. After plenty of searching online, and by common sense, I'm pretty sure HttpClient is barfing because of a problem establishing the mutually-authenticated SSL connection. But based on my troubleshooting I can't figure why.
To troublshoot further, I've written a plain old Console app using System.Net.Http.HttpClient, attached the client certificate to the request and everything works great. Sadly, System.Net isn't fully supported on UWP. I've also tried NOT attaching the certificate to the UWP HttpClient and the app prompts me with a UI to select an installed certificate. I select the correct cert and still get the same exception (this at least lets me know the cert is installed correctly and validating properly with the CA from the app's perspective). In additon, I hit the GET on the web service from a browser, select the client cert when prompted, and am able to download a file.
I've tried using Fiddler and, I assume because of the way it proxies traffic, it seems to work a little bit further, except my web service rejects the request as Forbidden (presumably because Fiddler is not including the correct client cert in the request). I haven't hit up Wireshark yet because it's a pain to get Wireshark to work using localhost on Windows.
My next step is to start changing the web service to not require client authentication and see if that is the problem.
Two questions: Why is Windows.Web.Http.HttClient not working in this case? And, less important, any recommendations on good HTTP monitoring tools to help me debug this further?
This MSDN post proved to have the answer. Seems like an oversight on MS part requiring a separate, meaningless call to the API beforehand. Oh well.
http://blogs.msdn.com/b/wsdevsol/archive/2015/03/26/how-to-use-a-shared-user-certificate-for-https-authentication-in-an-enterprise-application.aspx
Excerpt from the article:
However, the security subsystem requires user confirmation before allowing access to a certificates private key of a certificate stored in the shared user certificates store. To complicate matters, if a client certificate is specified in code then the lower level network functions assume the application has already taken care of this and will not prompt the user for confirmation.
If you look at the Windows Runtime classes related to certificates you won’t find any method to explicitly request access to the certificate private key, so what is the app developer to do?
The solution is to use the selected certificate to 'Sign' some small bit of data. When an application calls CryptographicEngine.SignAsync, the underlying code requests access to the private key to do the signing at which point the user is asked if they want to allow the application to access the certificate private key. Note that you must call 'Async' version of this function because the synchronous version of the function: Sign, uses an option that blocks the display of the confirmation dialog.
For example:
public static async Task<bool> VerifyCertificateKeyAccess(Certificate selectedCertificate)
{
bool VerifyResult = false; // default to access failure
CryptographicKey keyPair = await PersistedKeyProvider.OpenKeyPairFromCertificateAsync(
selectedCertificate, HashAlgorithmNames.Sha1,
CryptographicPadding.RsaPkcs1V15);
String buffer = "Data to sign";
IBuffer Data = CryptographicBuffer.ConvertStringToBinary(buffer, BinaryStringEncoding.Utf16BE);
try
{
//sign the data by using the key
IBuffer Signed = await CryptographicEngine.SignAsync(keyPair, Data);
VerifyResult = CryptographicEngine.VerifySignature(keyPair, Data, Signed);
}
catch (Exception exp)
{
System.Diagnostics.Debug.WriteLine("Verification Failed. Exception Occurred : {0}", exp.Message);
// default result is false so drop through to exit.
}
return VerifyResult;
}
You can then modify the earlier code example to call this function prior to using the client certificate in order to ensure the application has access to the certificate private key.
Add the Certificate file your Project
Add the Certificate to the Manifested file (give file path in attachment)
the Frist Service Call of in Ur Project use to ignore the certificate validation Following Code is most Suitable for Login Function.
try
{
var filter = new HttpBaseProtocolFilter();
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Expired);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.Untrusted);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.InvalidName);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.RevocationFailure);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.RevocationInformationMissing);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.WrongUsage);
filter.IgnorableServerCertificateErrors.Add(ChainValidationResult.IncompleteChain);
Windows.Web.Http.HttpClient client = new Windows.Web.Http.HttpClient(filter);
TimeSpan span = new TimeSpan(0, 0, 60);
var cts = new CancellationTokenSource();
cts.CancelAfter(span);
var request = new Windows.Web.Http.HttpRequestMessage()
{
RequestUri = new Uri(App.URL + "/oauth/token"),
Method = Windows.Web.Http.HttpMethod.Post,
};
//request.Properties. = span;
string encoded = System.Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(Server_Username + ":" + Server_Password));
var values = new Dictionary<string, string>
{ { "grant_type", "password" },{ "username", Uname}, { "password", Pwd }};
var content = new HttpFormUrlEncodedContent(values);
request.Headers.Add("Authorization", "Basic " + encoded);
request.Content = content;
User root = new User();
using (Windows.Web.Http.HttpResponseMessage response = await client.SendRequestAsync(request).AsTask(cts.Token))
{
HttpStatusCode = (int)response.StatusCode;
if (HttpStatusCode == (int)HttpCode.OK)
{
using (IHttpContent content1 = response.Content)
{
var jsonString = await content1.ReadAsStringAsync();
root = JsonConvert.DeserializeObject<User>(jsonString);
App.localSettings.Values["access_token"] = root.Access_token;
App.localSettings.Values["refresh_token"] = root.Refresh_token;
App.localSettings.Values["expires_in"] = root.Expires_in;
var json = JsonConvert.SerializeObject(root.Locations);
App.localSettings.Values["LocationList"] = json;
App.localSettings.Values["LoginUser"] = Uname;
}
}
}
}
catch (Exception ex)
{
ex.ToString();
}

Categories