Secure communication between android application and ASP.net api - c#

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.

Related

Choosing between accessing WCF via API Endpoint or via Dll reference

I have been contemplating on a dilemma for hours. I have a Visual Studio Solution that contains a WCF, WebForms, UWP, Xamarin and a SharedLibrary Projects.
I intend to use the WCF project as the backend which talks to the database and process Email and SMS integration and feed the other apps.
OPTION A
Currently, The WCF is hosted on an Azure App Service which makes it accessible via POST, GET, etc from the url which is: https://mywcfprojectlink.azurewebsites.net/service1.svc/GetUsers
With such arrangements, I can perform a POST request to get data from the apps:
string response = string.Empty;
string url = "https://mywcfprojectlink.azurewebsites.net/service1.svc/GetUsers";
try
{
var values = new Dictionary<string, string>
{
{ "data", Encryption.EncryptString(dat.ToString()) } //dat is incoming method param
};
string jsonString = JsonConvert.SerializeObject(values);
var cli = new WebClient();
cli.Headers[HttpRequestHeader.ContentType] = "application/json";
response = cli.UploadString($"{url}", jsonString);
var result = JsonConvert.DeserializeObject<string>(response);
topic.InnerText = Encryption.DecryptString(result.ToString());
}
catch (Exception)
{
return string.Empty;
}
The method above is a simple one as I have other ones where I Deserialize with Models/Classes.
OPTION B
I equally have access to the methods defined in service1 by adding the project reference to my WebForms which surprisingly is also compatible with xamarin but not with UWP. Nevertheless, I am interested in the WebForms scenario. Below is an example method:
using BackEnd;
//Service1 service1 = new Service1();
//var send = service1.GetUsers(dat.ToString()); //dat is incoming method param
//topic.InnerText = send;
Obviously, using the Option B would eliminate the need to encrypt, decrypt, serialize or deserialize the data being sent. However, I have serious performance concerns.
I need to know the better option and if there is yet another alternative (probably an Azure Resource), you can share with me.
If you decide to use https endpoint of the Azure website, option A is secure because of SSL encryption. So you don't have to encrypt/decrypt it by yourself. The only tip is to create a proper authorization mechanism. For example use TransportWithMessageCredential. An example is provided in below article https://www.codeproject.com/Articles/1092557/WCF-Security-and-Authentication-in-Azure-WsHttpBin

Ignore bad certificate - .NET CORE

I'm writing a .NET Core app to poll a remote server and transfer data as it appears. This is working perfectly in PHP because the PHP is ignoring the certificate (which is also a problem in browsers) but we want to move this to C# .NET CORE because this is the only remaining PHP in the system.
We know the server is good, but for various reasons the certificate can't / won't be updated any time soon.
The request is using HttpClient:
HttpClient httpClient = new HttpClient();
try
{
string url = "https://URLGoesHere.php";
MyData md = new MyData(); // this is some data we need to pass as a json
string postBody = JsonConvert.SerializeObject(md);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage wcfResponse = await httpClient.PostAsync(url, new StringContent(postBody, Encoding.UTF8, "application/json"));
Console.WriteLine(wcfResponse.Content);
}
catch (HttpRequestException hre)
{
// This exception is being triggered
}
Having researched this it seems the universal recommendation is to use ServicePointManager, but this is not available in .NET Core and I'm having trouble finding the recommended replacement.
Is there a simple or better way to do this in .NET Core?
Instead of new HttpClient() you want something akin to
var handler = new System.Net.Http.HttpClientHandler();
using (var httpClient = new System.Net.Http.HttpClient(handler))
{
handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) =>
{
// Log it, then use the same answer it would have had if we didn't make a callback.
Console.WriteLine(cert);
return errors == SslPolicyErrors.None;
};
...
}
That should work on Windows, and on Linux where libcurl is compiled to use openssl. With other curl backends Linux will throw an exception.
Getting Linux and macOS to work
If you are working in Linux or macOS you may encounter a scenario in which HttpClient will not allow you to access a self signed cert even if it is in your trusted store. You will likely get the following:
System.Net.Http.CurlException: Peer certificate cannot be authenticated with given CA certificates environment variable
of if you are implementing (as shown in the other answer)
handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) =>
{
// Log it, then use the same answer it would have had if we didn't make a callback.
Console.WriteLine(cert);
return errors == SslPolicyErrors.None;
};
This is due to the version of libcurl on the machine not supporting the appropriate callbacks that .Net Core needs to in order to gather the appropriate data to call the ServerCertificateCustomValidationCallback. For example, there isn't a way for the framework to create the cert object or another one of the parameters. More information can be found in the discussion for the workaround that was provided in .NET Core at issue in dotnet core's github repo:
https://github.com/dotnet/corefx/issues/19709
The workaround (which should only be used for testing or specific internal applications) is the following:
using System;
using System.Net.Http;
using System.Runtime.InteropServices;
namespace netcurl
{
class Program
{
static void Main(string[] args)
{
var url = "https://localhost:5001/.well-known/openid-configuration";
var handler = new HttpClientHandler();
using (var httpClient = new HttpClient(handler))
{
// Only do this for testing and potentially on linux/mac machines
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && IsTestUrl(url))
{
handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
}
var output = httpClient.GetStringAsync(url).Result;
Console.WriteLine(output);
}
}
static bool IsTestUrl(string url) => url.Contains("localhost");
}
}
There is another avenue for fixing this problem, and that is using a version of libcurl that is compiled with openssl support. For macOS, here is a good tutorial on how to do that:
https://spin.atomicobject.com/2017/09/28/net-core-osx-libcurl-openssl/
For the short version, grab a copy of the latest libcurl compiled with openssl support:
brew install curl --with-openssl
You probably don't want to force the entire OS to use the non-Apple version of libcurl, so you'll likely want to use the DYLD_LIBRARY_PATH environment variable instead of using brew to force link the binaries into the regular path of the OS.
export DYLD_LIBRARY_PATH=/usr/local/opt/curl/lib${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}
The above command can be used to set the appropriate environment variable when running dotnet in a terminal. This doesn't really apply to GUI applications though. If you're using Visual Studio for Mac, you can set the environment variable in the project run settings:
The second approach was necessary for me when using IdentityServer4 and token authorization. The .NET Core 2.0 authorization pipeline was making a call to the token authority using an HttpClient instance. Since I didn't have access to the HttpClient or its HttpClientHandler object, I needed to force the HttpClient instance to use the appropriate version of libcurl that would look into my KeyChain system roots for my trusted certificate. Otherwise, I would get the System.Net.Http.CurlException: Peer certificate cannot be authenticated with given CA certificates environment variable when trying to secure a webapi endpoint using the Authorize(AuthenticationSchemes = IdentityServerAuthenticationDefaults.AuthenticationScheme)] attribute.
I spent hours researching this before finding work arounds. My whole goal was to use a self-signed certificate during development for macOs using IdentityServer4 to secure my webapi. Hope this helps.
//at startup configure services add the following code
services.AddHttpClient(settings.HttpClientName, client => {
// code to configure headers etc..
}).ConfigurePrimaryHttpMessageHandler(() => {
var handler = new HttpClientHandler();
if (hostingEnvironment.IsDevelopment())
{
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; };
}
return handler;
});
now you can use IHttpClientFactory CreateClient method within your service
Just to add another variation, you could add in your thumbprint and check it in the callback to make things a bit more secure such as:
if (!string.IsNullOrEmpty(adminConfiguration.DevIdentityServerCertThumbprint))
{
options.BackchannelHttpHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (sender, certificate, chain, sslPolicyErrors) => certificate.Thumbprint.Equals(adminConfiguration.DevIdentityServerCertThumbprint, StringComparison.InvariantCultureIgnoreCase)
};
}
adminConfiguration.DevIdentityServerCertThumbprint is the configuration that you would set with your self signed cert's thumbrint.

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.

.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;

Bouncy Castle TLS API usage

I want to communicate between Server and Client using sockets using bouncy castle TLS library.
I went through many documentation(which was insufficient for me) but I did not get any idea how to do this,
I am using BouncyCastle v1.7.48(runtime version=v2.0.50727) binary,
and I have found these info,
I have to use, Org.BouncyCastle.Crypto.Tls namespace and TlsProtocolHandler class.
To achieve TLS communication,
what API I should use in server side?
what API I should use in client side?
System.IO.Stream inputStream, outputStream;
TlsProtocolHandler tls = new TlsProtocolHandler(inputStream, outputStream);
What are the parameters inputStream and outputStream?
public virtual void Connect(TlsClient tlsClient);
where, TlsClient is an interface, and that contains many interfaces inside.
4. How to use the above API? I have to declare new classes and implement methods inside that to all?
Please help me with this Bouncy Castle.
EDIT 1:
I created one class which inherits from an abstract class called DefaultTlsClient.
Then I could create an instance of my class and pass it for interface reference.
So I could send the parameter like this. tls.Connect(tlsClient);
I am not initializing any parameters except I mentioned above.
(Sockets are connected before these operation on 2055)
But I am not sure handshake is complete or not. My program will go to reading state.
There is no server-side TLS API in bouncy castle. You can read on main page that they support only client-side.
For client-side you have found right classes already. TlsProtocolHandler does the job, but it won't work without custom classes. Here is example code:
// Need class with TlsClient in inheritance chain
class MyTlsClient : DefaultTlsClient
{
public override TlsAuthentication GetAuthentication()
{
return new MyTlsAuthentication();
}
}
// Need class to handle certificate auth
class MyTlsAuthentication : TlsAuthentication
{
public TlsCredentials GetClientCredentials(CertificateRequest certificateRequest)
{
// return client certificate
return null;
}
public void NotifyServerCertificate(Certificate serverCertificate)
{
// validate server certificate
}
}
class Program
{
static void Main(string[] args)
{
TcpClient client = new TcpClient();
client.Connect(IPAddress.Loopback, 6000);
// input/output streams are deprecated, just pass client stream
TlsProtocolHandler handler = new TlsProtocolHandler(client.GetStream());
handler.Connect(new MyTlsClient());
// handshake completed
// use handler.Stream.Write/Read for sending app data
Console.ReadLine();
}
}
I have tested this with my tcp server and received client hello.
Keep in mind it is TLS in version 1.0 so if u need other version or server api then I recommend using other library (.NET framework supports TLS).

Categories

Resources