.NET Mutual SSL handshake 'Client Authentication' - c#

I have been working on this problem for the last couple of days and I am not getting anywhere.
The scenario is:
An iOS app in the field will call my REST Service (.NET).
My REST service will call the Apache web service using the Mutual SSL handshake.
Whatever data I receive I have to pass back to iOS devices in the field.
The only issue is the 2nd part of communication between My REST Service and Apache Web service.
Client Certificates have been signed using the key usages of Client authentication, Digital Certificate, Key Encipherment. The root signers of my certificate have been placed on the Apache server. If we try using the Web browser we can perform the handshake without any issues.
Sample code which I am using to perform the authentication using SslStream. Using this method I get an error stating
The message received was unexpected or badly formatted
and the guys who manage the Apache web server say that they can see the request coming in, but according to them they are not receiving a certificate with this.
var certificate = #“certificate.cer”;
var hostAddress = “hostAddress";
var certificates = new X509Certificate2Collection(new X509Certificate2(certificate));
RunClient(hostAddress, 1316, certificates);
static void RunClient(string hostName, int port, X509Certificate2Collection certificates)
{
TcpClient client = new TcpClient(hostName, port);
SslStream sslStream = new SslStream(client.GetStream(), false, ValidateServerCertificate);
try
{
sslStream.AuthenticateAsClient(hostName, certificates, SslProtocols.Ssl3, true);
Write("authenticated.");
}
catch (AuthenticationException ex)
{
Write("Inner: " + ex.InnerException.Message);
}
catch (Exception ex)
{
Write(ex.Message);
}
}
Sample code which I am using to perform the authentication using HttpWebRequest. Using this method gives me the following issue
The request was aborted: Could not create SSL/TLS secure channel.
var certificate = #“certificate.cer”;
var hostAddress = “hostAddress";
var certificates = new X509Certificate2Collection(new X509Certificate2(certificate));
public static T Post(string url, string body, X509Certificate2 cert)
{
var webRequest = FBJsonRequestService.CreateRequest(url, cert, WebRequestMethods.Http.Post, body);
using (var webResponse = webRequest.GetResponse())
{
return CreateResponse(webResponse);
}
}
var webRequest = (HttpWebRequest) WebRequest.Create(url);
webRequest.ClientCertificates.Add(cert);
//webRequest.AuthenticationLevel = AuthenticationLevel.MutualAuthRequested;
webRequest.Credentials = CredentialCache.DefaultNetworkCredentials;
webRequest.Method = method;
webRequest.ContentType = "application/json; charset=utf-8";
if (body != null)
{
using (var streamWriter = new StreamWriter(webRequest.GetRequestStream()))
{
streamWriter.Write(body);
}
}
return webRequest;
I hope this makes sense. All I want to know is if whatever I am doing is right or I am doing it wrong.

I think the content of file certificate.cer is incorrect. Extension .cer in my opinion contains only certificate but not private key.
You have to use certificate.p12 or certificate.pfx that contains also private key. These extensions represent PKCS#12 standard. There can also be whole certificate chain included in these files.
You can load a p12 file using different constructor of X509Certificate2 class. Please look at this documentation.

Thanks Pepo,
I figured this out that I was using the wrong file. I had to use the PFX file instead of CER. My issue turned out to be something else altogether. It was some security restriction of Certificate itself. Before posting this I wasn't even sure that whether I was doing the right thing.
I have created the following blog for someone in future needs any help regarding this.

The another reason for the exception "The request was aborted: Could not create SSL/TLS secure channel"
is that the server may not support the security protocol type.
The another protocol type is TLS whcih is more secure than SSL3,
The following code could help you
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;

Related

How to attach a non-exportable client certificate to a request using C# HttpClient?

I have a non-exportable client certificate in the "client" computer's Local Computer / Personal certificates store, and I want to use this certificate for client authentication when making web requests using System.Net.Http.HttpClient.
I have tried using HttpClientHandler thus:
var subject = /* Distinctive subject name of the certificate known to exist in the store */;
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, subject, true);
// Known/verifiable at this point that certificates[0] is the correct client cert
var clientHandler = new HttpClientHandler();
clientHandler.ClientCertificates.Add(certificates[0]);
using (var client = new HttpClient(clientHandler))
{
var response = await client.GetAsync(URL.Text);
var result = await response.Content.ReadAsStringAsync();
// ... etc...
}
... but the GetAsync call throws an exception with the message:
The request was aborted: Could not create SSL/TLS secure channel.
I have been able to verify that this is a client-side issue and that the correct certificate is being returned from the store.
I have tested this code against known working endpoints (which require a certificate); other clients work.
I know the certificate is valid because it is successfully used for client authentication by other client code (sources I don't currently have at my disposal)
As I understand it, I am only attaching the public portion (because it is non-exportable), so the authentication is failing. There must surely be a way to use a private, non-exportable key for such a purpose, correct? How do I do it?

TLS client certificate authentication on UWP Windows Store app

I'm trying to connect to a server that uses TLS with client certificate authentication. Below is a code snippet:
async Task TestClientCertAuth()
{
int iWinInetError = 0;
Uri theUri = new Uri("http://xxx-xxx");
try
{
using (HttpBaseProtocolFilter baseProtocolFilter = new HttpBaseProtocolFilter())
{
// Task<Certificate> GetClientCertificate() displays a UI with all available
// certificates with and returns the user selecter certificate. An
// oversimplified implementation is included for completeness.
baseProtocolFilter.ClientCertificate = await GetClientCertificate();
baseProtocolFilter.AllowAutoRedirect = false;
baseProtocolFilter.AllowUI = false;
using (HttpClient httpClient = new HttpClient(baseProtocolFilter))
using (HttpRequestMessage httpRequest = new HttpRequestMessage(HttpMethod.Get, theUri))
using (HttpResponseMessage httpResponse = await httpClient.SendRequestAsync(httpRequest))
{
httpResponse.EnsureSuccessStatusCode();
// Further HTTP calls using httpClient based on app logic.
}
}
}
catch (Exception ex)
{
iWinInetError = ex.HResult & 0xFFFF;
LogMessage(ex.ToString() + " Error code: " + iWinInetError);
throw;
}
}
// Task<Certificate> GetClientCertificate() displays a UI with all available
// certificates with and returns the user selecter certificate. An
// oversimplified implementation is included for completeness.
private async Task<Certificate> GetClientCertificate()
{
IReadOnlyList<Certificate> certList = await CertificateStores.FindAllAsync();
Certificate clientCert = null;
// Always choose first enumerated certificate. Works so long as there is only one cert
// installed and it's the right one.
if ((null != certList) && (certList.Count > 0))
{
clientCert = certList.First();
}
return clientCert;
}
The SendRequestAsync call throws an exception with HRESULT 0x80072F7D - I believe that means ERROR_INTERNET_SECURITY_CHANNEL_ERROR. There are no problems with the server certificate trust. The client certificate is installed in the app local store and I am abe to retrieve it using CertificateStores.FindAllAsync. Looking at the SSL traces and I can see the client certificate is not being sent.
The the above issue does not occur if HttpBaseProtocolFilter.AllowUI is set to true. In this case, the SendRequestAsync call causes a UI to be displayed asking for consent to use the client certificate. Once 'Allow' is selected on this dialog, I can see the client cert and cert verify messages being sent in the traces and the connection is established successfully.
Question: The app code already handles certificate selection by the user. I would like to know whether there is any way to specify consent to use the client certificate programmatically. Because enabling AllowUI causes other side effects - say for example if the server retruns a 401 HTTP code with a WWW-Authenticate: Basic header, the base protoctol filter pops up it's own UI to accept the user credentials without giving a chance for the caller to handle it. Would like to avoid both of the above UIs since I have already selected the client certificate and obtained credentials from the user with own UIs. Thanks
This Microsoft Blog Entry provides information on why this error occurs when AllowUI is false and provides a workaround. The certificate consent UI cannot be bypassed in any case, so that is something the EU has to go through. It also appears that the behavior on Windows Phone is different. Tried this solution and it seems to work on desktop and surface. The general idea is to 'prime' the certificate for use by the lower level APIs in the current app session by attempting to access the private key. In this case we just try to sign some dummy data. Once the EU grants access to the certificate, TLS session establishment goes through successfully. Need to check how
this behaves on Windows Phone though.
private async Task<bool> RequestCertificateAccess(Certificate cert)
{
bool signOK = false;
try
{
IBuffer data = CryptographicBuffer.ConvertStringToBinary("ABCDEFGHIJKLMNOPQRSTUVWXYZ012345656789",
BinaryStringEncoding.Utf8);
CryptographicKey key = await PersistedKeyProvider.OpenKeyPairFromCertificateAsync(cert,
HashAlgorithmNames.Sha1, CryptographicPadding.RsaPkcs1V15);
IBuffer sign = await CryptographicEngine.SignAsync(key, data);
signOK = CryptographicEngine.VerifySignature(key, data, sign);
}
catch (Exception ex)
{
LogMessage(ex.ToString(), "Certificate access denied or sign/verify failure.");
signOK = false;
}
return signOK;
}
RequestClientCertificateAccess can be called just before setting the client certificate on the base protocol filter.
#Tomas Karban, thanks for the response. I have not used sharedUserCertificates, so any certificate that I can enumerate has to be in the apps' certificate store if I understand correctly. The link I shared might be of some help for your case if you've not seen it already.
I would argue that you don't have the certificate stored in the app's own certificate store. If you set HttpBaseProtocolFilter.AllowUI = true and confirm the dialog, the app gets permission to use the private key from user store. Without the UI confirmation the app can only use private keys from its own certificate store.
The situation on Windows 10 Mobile is even worse -- as far as I can tell you cannot set HttpBaseProtocolFilter.AllowUI to true (see my question Cannot set HttpBaseProtocolFilter.AllowUI to true on Windows 10 Mobile). That leaves the only option to use app's own certificate store.

Mutual authentication using a USB token slot with a X.509 certificate

I am trying to implement a a client library in C# to communicate with a tomcat server. The authentication should be done by using a Feitian epass2003 token with a X.509 certificate inside mutual SSL authentication in a windows client.
However i am having a bad time cause everytime I run the client windows requests the token password in order to proceed the operation which is a overkill to the user and not acceptable.
I would like to know if its possible to dismiss this request and add it in some way in the code. Or if there is other way to use the token without using the windows cert manager.
this is how I am extending the connector:
class WebClientEx : WebClient
{
public int Timeout { get; set; }
public X509Certificate certificate { get; set; }
protected override WebRequest GetWebRequest(Uri address)
{
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
HttpWebRequest request = (HttpWebRequest)base.GetWebRequest(address);
request.Credentials = CredentialCache.DefaultCredentials;
request.AuthenticationLevel = AuthenticationLevel.MutualAuthRequested;
request.ClientCertificates.Add(this.certificate);
request.Timeout = this.Timeout;
return request;
}
...
}
This is an example how I am connecting to my server:
byte[] certificate = getCertificate("autenticacao"); // Get certificate from token
X509Certificate cert = new X509Certificate(certificate);
var post = new NameValueCollection();
post["test"] = "1";
using (var wb = new WebClientEx(cert))
{
try
{
wb.Timeout = 30000;
var response = wb.UploadValues("https://localhost:8443", "POST", post);
Console.WriteLine("Done.");
}
catch (WebException e)
{
// Handle WebException
}
}
This is windows request screen:
Best regards,
TLS allows for session reuse using a session ticket. See RFC5077 Transport Layer Security (TLS) Session Resumption without Server-Side State, read Speeding up SSL: enabling session reuse. Server support varies. Afaik the .Net Framework client side support is, basically, none. See TLS/SSL and .NET Framework 4.0.
Hardware modules do not allow the private key to ever leave the module and the PIN is required on each access. So if the TLS handshake requires the key, then the PIN dialog is unavoidable, your only chance is to try avoiding the private key requirement, and that is only doable with reusable TLS session tickets, afaik.
You may reconsider the mutual TLS requirement on each access. Access one resource (ie. login page), get an access ticket (cookie) then use this on authenticating accessing the rest of the resources.
PS. SSL is a no-option, been obsolete for years everybody talks about TLS nowadays.

Getting Exception from HRESULT: 0x80072F0D when posting to a service in Windows phone 8.1

When I am trying to post a data to an API using HttpClient in Windows Phone 8.1, I am always getting Exception from HRESULT: 0x80072F0D exception. In fiddler, it works fine.
try
{
var requestbody="json data"
HttpClient httpClient = new HttpClient();
HttpRequestMessage msg = new HttpRequestMessage(new HttpMethod("POST"), new Uri(addressUri));
msg.Content = new HttpStringContent(requestbody);
msg.Content.Headers.ContentType = new HttpMediaTypeHeaderValue("application/json");
HttpResponseMessage response = await httpClient.SendRequestAsync(msg).AsTask();
}
catch (Exception ex)
{
getting **Exception from HRESULT: 0x80072F0D**
}
Please tell me what went wrong?
---FYI----
For getting additional information about the HRESULT code : Follow this WebErrorStatus enumeration
var exceptionDetail = WebError.GetStatus(ex.GetBaseException().HResult);
if (exceptionDetail == WebErrorStatus.HostNameNotResolved)
{
//
}
This looks like a certificate related problem. Maybe you are using SSL. While lots of programs gracefully override missing certificates if not explicitly necessary (e.g.: browsers) the HttpClient is pretty sensitive against that.
You should try to download the certificate for the connection you're using and store the cert file in your assets folder. When your app starts, push it into the certificate store. This is a snippet I am using in one of my apps. Maybe this makes your exception go away.
Read more here: http://blogs.msdn.com/b/wsdevsol/archive/2014/06/05/including-self-signed-certificates-with-your-windows-runtime-based-windows-phone-8-1-apps.aspx
// Add our custom certificate
try
{
// Read the contents of the Certificate file
System.Uri certificateFile = new System.Uri("ms-appx:///Assets/ca.cer");
Windows.Storage.StorageFile file = await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(certificateFile);
Windows.Storage.Streams.IBuffer certBlob = await Windows.Storage.FileIO.ReadBufferAsync(file);
// Create an instance of the Certificate class using the retrieved certificate blob contents
Windows.Security.Cryptography.Certificates.Certificate rootCert = new Windows.Security.Cryptography.Certificates.Certificate(certBlob);
// Get access to the TrustedRootCertificationAuthorities for your own app (not the system one)
Windows.Security.Cryptography.Certificates.CertificateStore trustedStore = Windows.Security.Cryptography.Certificates.CertificateStores.TrustedRootCertificationAuthorities;
// Add the certificate to the TrustedRootCertificationAuthorities store for your app
trustedStore.Add(rootCert);
}
catch (Exception oEx)
{
// Catch that exception. We don't really have a choice here..
var msg = oEx.Message;
}
You might be able to bypass the error with this code:
var baseFilter = new HttpBaseProtocolFilter();
baseFilter.IgnorableServerCertificateErrors.Add(Windows.Security.Cryptography.Certificates.ChainValidationResult.InvalidCertificateAuthorityPolicy);
var httpClient = new HttpClient(baseFilter);
This merely silences the error rather than solving the problem, though. I'm not too knowledgeable with SSL errors, and this may not be a safe option, and may not pass app certification. According to the docs:
SSL server certificate errors should only be ignored in advanced scenarios. Disregarding server certificate errors classified as either Ignorable or Fatal may result in the loss of privacy or integrity of the content passed over the SSL session.
Received the same Error as originator. I do not use a proxy.
This worked for me. netsh winhttp reset proxy
Next Fax transmitted without error.
Experienced the 0x80072efd problem. Has cost me hours if not days to solve. The solution that gave instant resolution is the following command from a admin command prompt:
netsh winhttp reset proxy

Using client certificate not in certificate store

I'm trying to authenticate myself against WebService using my client certificate, but, for some reasons (I explain), I don't want to load certificate from store, rather read it from disc.
The following:
// gw is teh WebService client
X509Certificate cert = new X509Certificate(PathToCertificate);
_gw.ClientCertificates.Add(ClientCertificate());
ServicePointManager.ServerCertificateValidationCallback = (a,b,c,d) => true;
_gw.DoSomeCall();
returns always 403 - the Service doesn't authorize me. But, when I save that certificate into CertStore, it works. (As stated in MSDN.)
Is it possible to use certificate not in store?
(the reason is, that I got windows service(client) sometimes calling webservice(server), and after unspecified amount of time the service 'forgets' my certificates and doesnt authorize against server, with no apparent reason)
What type of file is PathToCertificate? If it's just a .cer file, it will not contain the private key for the certificate and trying to use that certificate for SSL/TLS will fail.
However, if you have a PKCS7 or PKCS12 file that includes the public and private key for the certificate, your code will work (you might need to use the overload that takes a password if the private key has one).
To test this, I went to http://www.mono-project.com/UsingClientCertificatesWithXSP and created my client.p12 file following those instructions. I also created a simple HTTPS server using HttpListener for testing.
Then I compiled the following program into 'client.exe' and run like:
client.exe https://<MYSSLSERVER>/ client.p12 password
where client.p12 is the PKCS12 file generated before and 'password' is the password I set for the private key of the certificate.
using System;
using System.IO;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text;
public class HttpWebRequestClientCertificateTest : ICertificatePolicy {
public bool CheckValidationResult (ServicePoint sp, X509Certificate certificate,
WebRequest request, int error)
{
return true; // server certificate's CA is not known to windows.
}
static void Main (string[] args)
{
string host = "https://localhost:1234/";
if (args.Length > 0)
host = args[0];
X509Certificate2 certificate = null;
if (args.Length > 1) {
string password = null;
if (args.Length > 2)
password = args [2];
certificate = new X509Certificate2 (args[1], password);
}
ServicePointManager.CertificatePolicy = new HttpWebRequestClientCertificateTest ();
HttpWebRequest req = (HttpWebRequest) WebRequest.Create (host);
if (certificate != null)
req.ClientCertificates.Add (certificate);
WebResponse resp = req.GetResponse ();
Stream stream = resp.GetResponseStream ();
StreamReader sr = new StreamReader (stream, Encoding.UTF8);
Console.WriteLine (sr.ReadToEnd ());
}
}
Let me know if you want me to upload the server code and the certificates used on both sides of the test.
The potential problem could be caching of SSL sessions (Schannel cache). Only first request negotiates the SSL handshake. Subsequent requests will use the same session ID and hope that the server accept it. If the server clears the SessionId, the requests will fail with 403 error. To disable local ssl session caching (and force SSL negotiation for each request) you have to open windows registry folder:
[HKEY_LOCAL_MACHINE][System][CurrentControlSet][Control][SecurityProviders][SCHANNEL]
and add the key named ClientCacheTime (DWORD) with value 0.
This issue is covered here:
http://support.microsoft.com/?id=247658
You have the potential for at least two problems...
First...
Your client certificate file cannot contain a private key unless it's accessed with a password. You should be using a PKCS #12 (*.pfx) certificate with a password so that your client has access to the private key. You client code will have to provide the password when opening the certificate as others have already posted. There are several ways to create this, the easiest is to use the following command-line to first generate the certificate, then use the MMC certificate manager to export the certificates private key:
Process p = Process.Start(
"makecert.exe",
String.Join(" ", new string[] {
"-r",// Create a self signed certificate
"-pe",// Mark generated private key as exportable
"-n", "CN=" + myHostName,// Certificate subject X500 name (eg: CN=Fred Dews)
"-b", "01/01/2000",// Start of the validity period; default to now.
"-e", "01/01/2036",// End of validity period; defaults to 2039
"-eku",// Comma separated enhanced key usage OIDs
"1.3.6.1.5.5.7.3.1," +// Server Authentication (1.3.6.1.5.5.7.3.1)
"1.3.6.1.5.5.7.3.2", // Client Authentication (1.3.6.1.5.5.7.3.2)
"-ss", "my",// Subject's certificate store name that stores the output certificate
"-sr", "LocalMachine",// Subject's certificate store location.
"-sky", "exchange",// Subject key type <signature|exchange|<integer>>.
"-sp",// Subject's CryptoAPI provider's name
"Microsoft RSA SChannel Cryptographic Provider",
"-sy", "12",// Subject's CryptoAPI provider's type
myHostName + ".cer"// [outputCertificateFile]
})
);
Second...
Your next problem is going to be server-side. The server has to allow this certificate. You have the right logic, but on the wrong side of the wire, move this line to the web server handling the request. If you cannot, you must then take the '.cer' file saved above to the server and add it to the server computer's trust list:
ServicePointManager.ServerCertificateValidationCallback = (a,b,c,d) => true;
Do you need a password for the certificate? If so, there is a field for it in the constructor.
X509Certificate cert = new X509Certificate(PathToCertificate,YourPassword);

Categories

Resources