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();
}
Related
My scenario:
I have an EC2 instance running Windows Server 2016 DataCenter OS in it. This EC2 instance is configured to work as CA and IIS server (I am testing cloudhsm so it is okay for me to have multiple services in the same server). My CA is configured with RSA#Cavium Key Storage Provider (as it supports AWS CloudHSM Key Storage Provider). Now from my sample .Net WebAPI app, I can create a CSR using below code:
private async Task<CertificateSigningResponse> ActualSigningAsync(CertificateSigningRequest csr)
{
CertificateSigningResponse certificateSigningResponse;
try
{
using (RSA rsa = RSA.Create(csr.KeySize))
{
CertificateRequest request = new CertificateRequest("CN=servername-CA2", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
OidCollection oidCollection = new OidCollection();
oidCollection.Add(new Oid("1.3.6.1.5.5.7.3.8"));
request.CertificateExtensions.Add(new X509BasicConstraintsExtension(true, false, 0, true));
request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(oidCollection, true));
request.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(request.PublicKey, false));
var serialNumberInBytes = System.Text.Encoding.UTF8.GetBytes(Guid.NewGuid().ToString());
/*
// below line creates a self signed certificate
var selfSignedCertificate = request.Create(
new X500DistinguishedName("CN=servername-CA2"),
X509SignatureGenerator.CreateForRSA(rsa, RSASignaturePadding.Pkcs1),
DateTimeOffset.Now,
DateTimeOffset.Now.AddMonths(1),
serialNumberInBytes);
*/
var certSigningRequest = request.CreateSigningRequest(X509SignatureGenerator.CreateForRSA(rsa, RSASignaturePadding.Pkcs1));
if (certSigningRequest != null)
{
certificateSigningResponse = PopulateCertificateSigningResponse(JsonConvert.SerializeObject(certSigningRequest), $"Sample Cert with Sl#: {certSigningRequest}");
}
else
{
certificateSigningResponse = PopulateCertificateSigningResponse("", $"Error: {request.PublicKey}");
}
}
}
catch (Exception ex)
{
certificateSigningResponse = PopulateCertificateSigningResponse(string.Empty, ex.Message);
}
return certificateSigningResponse;
}
Now, I want to send this CSR to my CA (which is running on EC2) and get it signed. Once signed, I want to send the signed certificate back to the user. Currently, I have hosted my sample webapi app on the same EC2 instance. This app actually exposes an endpoint, which I can hit via postman and pass in some data as request body (POST).
So, ideally, the app receives my POST request and extracts data from it, then using that data, it is creating a csr. And now I want to send this csr to my CA, get it signed and finally return the signed certificate to user. Below pseudo code may give you some idea about my requirements:
//pseudo code; not existed
// request is generated in above code
var caService = new CAService();//can use my ca info i.e. name, public dns, ip
var response = await caService.SignAsync(request);//response should be a cert
return response;
I have seen few posts using CertUtilLib/CertEnrollLib but I want to achieve this by fully managed code. Also if it is not possible, please suggest me some alternatives. Thanks.
EDIT 1 (after Michal's comment)
Below are some info for your interest:
Current Scenario:
we are using a 3rd party API to sign some critical data.
In this way, we make a POST request to that particular endpoint with the data we wanted to be signed.
The 3rd party API handles everything for us behind the scene. We know that they are using some HSM service at their end to sign data.
My intention:
I am trying to achieve the same functionality using AWS CloudHSM.
The calling process should be same, i.e. we should still call an endpoint with data and get the signed copy of the data.
For this, my understanding was to create the CSR at server end and sign it using CloudHSM from within the server.
For that, I have created a sample .Net API app that exposes an endpoint where I can post my data to be signed.
Then it is my app's responsibility to create the CSR (I did it already), and then send the CSR to a CA (this is my EC2 Windows
Server 2016 instance which has the CA feature enabled on it) for
signing (I am stuck here at the moment).
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).
Making my first steps at implementing payment gateways.
Calling both Braintree and Beantream payment gateways from my ASP.net code behind hosted in IIS 8.5 under win 8.1 results in :
"The underlying connection was closed: Could not establish trust
relationship for the SSL/TLS secure channel"
Note that calling the same services from my middle-tier that sits on the same box works fine, so the certificate is installed properly.
Here's sample code to call the Braintree service thru their SDK:
[System.Web.Services.WebMethod]
public static CheckinDetailResponse GetViewData()
{
CheckinDetailResponse response = new CheckinDetailResponse()
{
ClientToken = string.Empty,
IsSuccessful = false,
ErrorMessage = string.Empty
};
try
{
response.ClientToken = GetPaymentToken();
response.IsSuccessful = true;
}
catch (BaseDataServicesException ex)
{
response.ErrorMessage = ex.GetLocalizedMessage(Resources.WebCommon.ResourceManager);
}
return response;
}
private static string GetPaymentToken()
{
var gateway = new BraintreeGateway
{
Environment = Braintree.Environment.SANDBOX,
MerchantId = "999",
PublicKey = "999",
PrivateKey = "999"
};
var clientToken = gateway.ClientToken.generate(null);
return JsonDcSerialization.ToJsonString(clientToken); ;
}
Is there some special setting that needs to be done in IIS in order to call SSL services from a web app?
Thanks!
Check if the SSL Certificates are installed on the machine, if you do pass them on you code, and also if the machine you're using is configured to accept the certificate issuer as trusted.
if this is a certificate issued from a third party certification authority you may need to pass the signer certificate (aka intermediate ca) along with the public key (eventually in pkcs#7 format). In the case you're using a self-signed certificate you may need to have the root imported into the mmc trusted root certification authority store.
I tend to think it's the first case as untrusted error message is usually referred to the lack of a piece in the chain of trust.
Hope this help.
I'm trying to implement Https Client Authentication in my application but I am having trouble finding any documentation on how to do it.
Looking through the MSDN documents I came up with this
// Certificate file in DER format (.cer or .p7b)
string CountriesFile = #"Assets\https-client.keystore.cer";
StorageFolder InstallationFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
StorageFile file = await InstallationFolder.GetFileAsync(CountriesFile);
// Read the file into a buffer
IBuffer buffer = await Windows.Storage.FileIO.ReadBufferAsync(file);
// Create the Certificate object
Certificate ClientCert = new Certificate(buffer);
HttpBaseProtocolFilter aHBPF = new HttpBaseProtocolFilter();
aHBPF.ClientCertificate = ClientCert;
// Create our http client and send the request.
HttpClient httpClient = new HttpClient(aHBPF);
HttpResponseMessage response = await httpClient.SendRequestAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead).AsTask(cts.Token);
I put this code together looking at the docs for HttpClient, HttpBaseProtocolFilter and Certificate. Making the assumption that I should have the certificate in the required format and read the file into the Certificate class.
The above code doesn't work and throws this error
An exception of type 'System.ArgumentException' occurred in MyLib.DLL but was not handled in user code
WinRT information: The certificate specified is missing the required private key information.
I have tested my server set-up and it works with client auth through a browser, which leads me to two possible conclusions.
The certificate file is in the wrong format (though I would have hoped the exception would get thrown when the Certificate class is constructed).
This is not intended way to do it!
Any one know how it should be done?
It would appear that you have to install the certificate at a User level before you can effectively use it for client authentication within a Windows Store App
// Needs to be a PKCS12 (p12/pfx) file
string certPath = #"Assets\https-client.keystore.p12";
StorageFile file = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(certPath);
IBuffer buffer = await FileIO.ReadBufferAsync(file);
string certData = CryptographicBuffer.EncodeToBase64String(buffer);
// Will ask the user if they want this app to install the certificate if its not already installed.
await CertificateEnrollmentManager.UserCertificateEnrollmentManager.ImportPfxDataAsync(
certData,
"PASSWORD",
ExportOption.NotExportable,
KeyProtectionLevel.NoConsent,
InstallOptions.None,
"MyFriendlyName");
Now the certificate is installed, it will be available to us in the certificate store.
var certificate = await CertificateStores.FindAllAsync(new CertificateQuery() { FriendlyName = "MyFriendlyName" });
ClientCert = certificate.Single();
HttpBaseProtocolFilter aHBPF = new HttpBaseProtocolFilter();
aHBPF.ClientCertificate = ClientCert;
// Create our http client and send the request.
HttpClient httpClient = new HttpClient(aHBPF);
HttpResponseMessage response = await httpClient.SendRequestAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead).AsTask(cts.Token);
I would prefer to be able to make the certificate available to only the application and will update this answer if I find a way of doing it.
We have a WCF service configured to work with client certificates.
Our client is a .net 3.5 WPF application.
While testing, it works perfectly with certificates generated by Microsoft CA and others.
While testing it with a physical token (Aladdin/Safenet eToken Pro 64k) it also works well.
(On first try connecting to server the “Token Logon” window pops up.
After successful authentication to the token the server request works, and the next try to connect to server succeed without the token logon message showing)
Now, if we remove the token and re-insert it, when trying to connect with the same certificate we get an error “The request was aborted: Could not create SSL/TLS secure channel”. HResult 0x80131509
The only way to make it work again is restart the application.
Certificates are retrieved with System.Security.Cryptogragpy:
var store = new X509Store("My", StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
//Getting the right certificate and then:
store.Close();
The connection to server is with this code:
string uriStr = txtUrl.Text + "/rest/auth/strong/Ping";
var client = WebRequest.Create(new Uri(uriStr)) as HttpWebRequest;
client.Method = "POST";
client.ContentType = "application/text; charset=unicode;";
client.ContentLength = 0;
client.PreAuthenticate = false;
client.KeepAlive = false;
client.ClientCertificates.Add(certificate); //Cert attached to the request
using (var res = client.GetResponse() as HttpWebResponse)
{
using (var responseStream = res.GetResponseStream())
{
using (var reader = new StreamReader(responseStream))
{
string s = reader.ReadToEnd();
ShowAndWriteToFile(s);
}
}
}
I looked at Verbose logging and Wireshark captures, Couldn't find the problem from there.
Other stuff I tried:
Clearing pin code cache – when clearing it before removing token, then on next attempt to connect to server the “Token logon” window pops up.
Clearing it after remove and re-insert didn’t change anything.
Restarting the WCF Service – after the token re-inserted, still same result.
Checking how other operations on the certificate behaves before and after token remove and re-insert.
For instance, if I Sign and verify some message with the certificate after the removal and re-insert of the token then the “Token logon” window pops up again and it continues working.
Trying to connect with self-created certificate put in the store – Worked, then deleting it from store and re-adding it – still worked.