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

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.

Related

Client terminate SSL Connection after server sends SERVER HELLO DONE

It is my first question on Stack Overflow, I'll do my best !
I have already read multiple document on how SLL/TLS connection works. I didn't found any clue of what is happening.
I am currently connecting to a server through SSL.
My OS is Windows 10.
The server ask us a certificate which we provide.
Everything fine there. The SSL connection is established.
Now, when I try to establish the connection from my server, I receive a Web Exception ("The request was aborted: Could not create SSL/TLS secure channel.")
The server's OS is Windows Server 2012 R2.
I am pretty sure our server closes the connection (See attached picture at the bottom of the post).
Code to load the certificate
X509Certificate cubicCertificate;
try
{
cubicCertificate = X509CertificateHelper.GetCertificate2FromStore(cubicCertificateThumbPrint);
}
catch(Exception e)
{
m_Log.Trace("Cubic Http client initialization - Could not load certificate");
throw e;
}
var handler = new WebRequestHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ClientCertificates.Add(cubicCertificate);
ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
m_HttpClient = new HttpClient(handler);
m_HttpClient.Timeout = new TimeSpan(0, 0, 30); // 30 seconds timeout
m_HttpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Code to send a request
try
{
var requestMessage = new HttpRequestMessage(HttpMethod.Post, $"{cubicUrl}");
requestMessage.Content = new FormUrlEncodedContent(parameters);
m_Log.Trace($"URL - {m_HttpClient.BaseAddress}{url}");
Task <HttpResponseMessage> task = m_HttpClient.SendAsync(requestMessage);
responseMessage = task.Result;
}
catch(Exception e)
{
var serializedException = JsonConvert.SerializeObject(e);
m_Log.Trace(serializedException);
throw e;
}
We have already verified
That the certificate sent by the distant server is recognize by our server.
That our server can load our certificate. (It is loaded from the Store)
That our server can access the private key of our certificate
Any help is welcome !
Seems the remote server is enforcing Client Authentication (see Certificate Request) and probably it does not trust your certificate.

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.

Reuse EWS connection and negotiate authentication for Exchange

I am writing a program to dump the contents of numerous mailboxes from an Exchange server using EWS in C#. Using fiddler I noticed that each request I send makes a new connection (tunnel), with a new authentication process being undertaken (using negotiate). My ServerCertificateValidationCallback gets called for every request.
If I enable the option in Fiddler to "reuse server connections" than the connection is only created during handshaking, and is re-used for all requests (saving lots of time).
By getting the EWS source and modifying the requests I found if I enable "UnsafeAuthenticatedConnectionSharing" on the request objects than the connection is re-used (extra tunnels & ServerCertificateValidationCallbacks disappear), but each request still requires the full handshake authentication. This is because the server sends back a 401 when ever I try and use the exchange cookie.
Is there any way I can re-use my server connection & authentication?
public class EwsExchange
{
static int Main(string[] args)
{
sslCertCheckCount = 0;
ServicePointManager.ServerCertificateValidationCallback = ServerCertificateValidation;
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP1);
service.Credentials = new NetworkCredential(args[1], args[2]);
service.Url = new Uri(args[0] + #"/EWS/exchange.asmx");
service.KeepAlive = true;
service.PreAuthenticate = true;
//service.UnsafeAuthenticatedConnectionSharing = true;
Folder folder = Folder.Bind(service, WellKnownFolderName.Inbox, new PropertySet(FolderSchema.Id, FolderSchema.DisplayName));
FindItemsResults<Item> res = folder.FindItems(new ItemView(int.MaxValue));
return 0;
}
public static bool ServerCertificateValidation(Object obj, X509Certificate certificate, X509Chain chain, System.Net.Security.SslPolicyErrors errors)
{
Console.WriteLine(String.Format(" ****************** ServerCertificateValidation - count: {0}. ****************** ", ++sslCertCheckCount));
return true;
}
static int sslCertCheckCount;
}
Thanks!
So it turns out that after I modified the EWS API to allow me to enable UnsafeAuthenticatedConnectionSharing on the HttpRequests, that my connection and authentication were actually being re-used.
Fiddler was the one dropping my connections after I disabled the option "Tools -> Fiddler Options -> Connections -> Reuse server connections". Running wireshark on the exchange server machine showed that when fiddler was capturing with this option disabled the FIN TCP flag was being set, ending the session. But without fiddler capturing the connection & session were both re-used.

.NET Mutual SSL handshake 'Client Authentication'

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;

Secure communication between android application and ASP.net api

My Android application is supposed to communicate with a ASP.net web api which is written in C#.Net. The data which is sent from the phone contains data that should not be exposed to the public. So I'm trying to use the https protocol. On my serverside I require all requests to be HTTPS, like this:
RequireRegisteredImei
public class RequireRegisteredImeiAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
var request = actionContext.ControllerContext.Request;
if (request.RequestUri.Scheme == Uri.UriSchemeHttps)
{
//OKAY
}
else
{
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden);
}
}
}
And in the Controller:
[RequireRegisteredImei]
public string Post()
{
}
I debugged this code by sending a simple http request from my phone, and this code works quite well, it will deny me.
So, I started looking at how I could send requests over https from my android phone. I came up with something like this:
public static DefaultHttpClient getSecureHttpClient() {
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
schemeRegistry.register(new Scheme("http", SSLSocketFactory.getSocketFactory(), 80));
BasicHttpParams params = new BasicHttpParams();
SingleClientConnManager mgr = new SingleClientConnManager(params, schemeRegistry);
return new DefaultHttpClient(mgr, params);
}
I'm using this method this way:
HttpClient httpClient = CustomHttpClient.getSecureHttpClient();
This will only result in an IOException: No peer certificate
I've read several threads regarding this:
Problems with https (No peer certificate) in android
Android SSL - No Peer Certificate
'No peer certificate' error in Android 2.3 but NOT in 4
But there has to be a simpler way to post data over HTTPS from android?
If you have a custom certificate or a certificate issued by a CA that is not included in all Android versions you can include the certificate into your app and use it directly.
To do so you have to import your server certificate (without the key of course) into a BKS key-store which then can be used as custom trust store.
A very good tutorial which describes how to do so is Using a Custom Certificate Trust Store on Android.
In difference to the standard solutions like EasyTrustManager or DummyTrustManager you find of Stackoverflow this solution doesn't disable the authentication of SSL and is therefore not insecure like the other solutions.
You can also configure the HttpClient to accept all certificates like this :
SSLSocketFactory sslSocketFactory=SSLSocketFactory.getSocketFactory();
HostnameVerifier hostnameVerifier=org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;
sslSocketFactory.setHostnameVerifier((X509HostnameVerifier) hostnameVerifier);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(
new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(
new Scheme("https", sslSocketFactory, 443));
if you think that this may be a solution for you.

Categories

Resources