Finding a way to avoid IP spoofing on application authorization - c#

I've developed C# application, which uses authorization via TCP. It connects to server application (located on amazon VPS), sends authorization string and gets response. The TCP information exchange flow is encrypted via TLS1.2 and also additionally with Rijndael symmetric encryption. Also inside the client application there is a check for SSLStream on RemoteCertificateValidationCallback where thumbprint of the ssl certificate obtained from serer is compared with the hardcoded one. The client applcation is obfuscated with .NetReactor, also with option to produce native .exe, and this exe additionally is compressed with Themida. But the hacker has somehow cracked the protocol and created his own fake authorization server and selling the illegal copy of program to his "clients". Each time I change the encryption secret key, he immediately goes out with new update, so it is infinite struggle. He doesn't need to crack the client .exe, he just updates the server on his side. I know he uses Windows standart postproxy to redirect the traffic, I've made addtional checks inside the client exe for existance of windows postproxy records in registry, but it didn't do any effect.
So my question is: is there any way in C# to check if application connects to the "correct" server and not the "fake" one? I'm using standard TcpClient class for authorization, but can migrate to something else if it can help.

As #00110001 mentions, you probably have more issues than this, but maybe public key pinning can help you.
Assuming that your binary is signed (i.e. cannot easily be altered after downloading directly from your website, without incurring warnings during start):
ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) =>
{
if (sender is HttpWebRequest httpWebRequest
&& httpWebRequest.RequestUri.Host.Contains("<your pinned domain>", StringComparison.OrdinalIgnoreCase))
{
return _allowedPublicKeys.Contains(certificate?.GetPublicKeyString());
}
else if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}
return false;
};
But if the "hacker" is re-publishing your binaries with their changes, which it seems like the person does as they point to another authorization server, you're out of luck.
Edit: If you're using HttpClient and need only to pin some outbound requests, you can do something like:
internal HttpClient GetSecureHttpClient()
{
var handler = new HttpClientHandler();
handler.ClientCertificateOptions = ClientCertificateOption.Manual;
handler.ServerCertificateCustomValidationCallback = (httpRequestMessage, certificate, chain, sslPolicyErrors) =>
{
if (httpRequestMessage.RequestUri.Host.Contains("<your pinned domain>", StringComparison.OrdinalIgnoreCase))
{
return _allowedPublicKeys.Contains(certificate?.GetPublicKeyString());
}
else if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}
return false;
};
var client = new HttpClient(handler);
return client;
}
// use:
var _ = await GetSecureHttpClient().GetAsync("https://google.com");
See dotnetfiddle example: https://dotnetfiddle.net/q1ggDS

Related

The remote certificate was rejected by the provided RemoteCertificateValidationCallback - how to get more details?

I have prepared a test case for my problem - a very simple .NET 6 Console app:
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
namespace CheckCert
{
internal class Program
{
public const string jsonUrl = "https://wordsbyfarber.com/de/top-5";
static void Main(string[] args)
{
Console.WriteLine($"jsonUrl = {jsonUrl}");
HttpClientHandler handler = new()
{
ServerCertificateCustomValidationCallback = BackendCaValidation,
CheckCertificateRevocationList = true,
};
HttpClient httpClient = new(handler);
string jsonStr = httpClient.GetStringAsync(new Uri(jsonUrl)).Result;
Console.WriteLine($"jsonStr = {jsonStr}");
}
private static bool BackendCaValidation(HttpRequestMessage message,
X509Certificate2? certificate,
X509Chain? chain,
SslPolicyErrors sslPolicyErrors)
{
Console.WriteLine($"sslPolicyErrors = {sslPolicyErrors}");
return SslPolicyErrors.None == sslPolicyErrors;
}
}
}
When I run it, it works as expected, will print SslPolicyErrors.None and the JSON content from my private website, which uses a Let's Encrypt certificate.
However when I change the jsonUrl to the URL of my work server, which I am better not sharing in public, then I end up with SslPolicyErrors.RemoteCertificateChainErrors and a System.AggregateException.
The exception says "look at the inner exception".
So inspect the inner exception and it says:
The remote certificate was rejected by the provided RemoteCertificateValidationCallback
So I keep looking at the certificate and the chain displayed by the Microsoft Edge browser - both for my private website and for the work server.
The work server uses a certificate issued by a self-signed work CA (and there is an intermediate certificate inbetween). All 3 certificates are not expired yet.
My question is: how to get more information here?
Why exactly do I get a SslPolicyErrors.RemoteCertificateChainErrors? is it because of that self-signed corporate CA or maybe because of some signing algorithm?
And also - similar code works for us in another project (an Azure Service Fabric application) without failing. I wonder, what could be the difference?
UPDATE:
I have followed the suggestion by Mr. Spiller (thank you!) and have added the code:
Console.WriteLine("-----------------------------------");
foreach (X509ChainStatus status in chain.ChainStatus)
{
Console.WriteLine($"status = {status.Status}");
}
Now my private Let's Encrypt secured URL looks like this (why is there no chain printed? I can see the chain in the web browser):
And the "faulty" corporate URL looks like this:
My main question is: how to make my app work against the corporate URL, without making it insecure?
I.e. I would probably have to accept the returned SslPolicyErrors.RemoteCertificateChainErrors in my app, but can I still perform some checks?
The parameter X509Chain? chain has a property ChainStatus which you can use to get the status for each element of the certification chain.
Each element in turn has a property Status of type System.Security.Cryptography.X509Certificates.X509ChainStatusFlags (cf. documentation) that should give you the status of each particular element of the certification chain.
In your case one (the only?) element most likely has the status UntrustedRoot.
If you want to connect to your corporate server even though the certificate is not trusted, you can simply return true from the callback. I.e. in BackendCaValidation check whether you are talking to the corporate server and return true even though sslPolicyErrors is not None.
The other (preferred?) way is to trust your corporate CA system-wide. I.e. add the CA to the cert store of your operating system and mark it as trusted.

ASP.NET Web API: verify if client has specific certifcate installed

We want to create a self signed certificate and manually install it on client PC's, with the private key marked as non-exportable.
The client calls a ASP.NET Web API, and there we want to check if that specific certificate is installed.
We want to do this as an additional method of authentication, to make sure that only valid clients can call the Web API. We know this is'nt waterproof because it is still possible to export the private key, but we have other additional authentication mechanisms such as a user password.
How can we achieve this? Or are there better ways to achieve our goal?
For Me After I created the self signed certificate in the Server , I added he Certificate Thumbprint to my Client App
private void setAppLocalSSL()
{
ServicePointManager.ServerCertificateValidationCallback += delegate (
object sender,
X509Certificate cert,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true; //Is valid
}
// this is the Thumbprint of my local cert
// your Cert Thumbprint instead of "0000000000000000"
if (cert.GetCertHashString().ToLower() == "0000000000000000")
{
return true;
}
return false;
};
}
call setAppLocalSSL onload
Note : this is a WPF App.

Accept only self signed SSL-Certificate C#

I have a selfprogrammed API running on a Windows Server 2012 with a self signed SSL-Certificate. Now I want to communicate with the webservice via HTTPS.
The comunication is only at a local network, but i still want the conection to be secure.
Is there a way to accept only my self signed certificate? I found a lot of solutions to accept all certificates, but I only want mine to be accepted.
I already thought about adding it to the windows accepted certificates, but since the program consuming the webservice is user by serveral users on diffrent PC's and I do not have administration rights on all off them.
Is it even possible to have a secure connection the way I want it to be?
Yes, as Gusman suggests, you should implement your own method for the ServerCertificateValidationCallback. You can compare the thumbprint of the cert to validate whether it is the one you want to trust. Something like this should work:
public static class CertificateValidator
{
public static string TrustedThumbprint { get; set; }
public static bool ValidateSslCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors errors)
{
// Wrap certificate to access thumbprint.
var certificate2 = new X509Certificate2(certificate);
// Only accept certificate with trusted thumbprint.
if (certificate2.Thumbprint.Equals(
TrustedThumbprint, StringComparison.OrdinalIgnoreCase))
{
return true;
}
// In all other cases, don't trust the certificate.
return false;
}
}
In your startup code, include the following to wire this up:
// Read your trusted thumbprint from some secure storage.
// Don't check it into source control! ;-)
CertificateValidator.TrustedThumbprint = SomeSecureConfig.Get["TrustedApiThumbprint"];
// Set the callback used to validate certificates.
ServicePointManager.ServerCertificateValidationCallback += CertificateValidator.ValidateSslCertificate;

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();
}

Best practices for using ServerCertificateValidationCallback

I am working on a project that uses some HTTP communication between two back-end servers. Servers are using X509 certificates for authentication. Needless to say, when server A (client) establishes connection to server B (server), there is a SSL/TLS validation error, since certificates used are not from trusted 3rd party authority.
Normally, the way to handle it is using ServicePointManager.ServerCertificateValidationCallback, such as:
ServicePointManager.ServerCertificateValidationCallback +=
(sender, cert, chain, error) =>
{
return cert.GetCertHashString() == "xxxxxxxxxxxxxxxx";
};
That approach works, except it's not ideal. What it essentially does is override validation procedure for EVERY http request done by the application. So, if another class will try to run HTTP request, it will fail. Also, if another class overrides ServicePointManager.ServerCertificateValidationCallback for its own purposes, then my communication starts failing out of sudden.
The only solution which comes to mind, is creating a separate AppDomain to perform client HTTP requests. That would work, but really - it's silly to have to do that only so that one can perform HTTP requests. Overhead will be staggering.
With that in mind, have anyone researched if there is a better practice in .NET, which would allow accessing web services, while handling client SSL/TLS validation without affecting other web clients?
An acceptable (safe) methodology working in .NET 4.5+ is to use HttpWebRequest.ServerCertificateValidationCallback. Assigning that callback on a specific instance of request will change the validation logic just for the request, not influencing other requests.
var request = (HttpWebRequest)WebRequest.Create("https://...");
request.ServerCertificateValidationCallback +=
(sender, cert, chain, error) =>
{
return cert.GetCertHashString() == "xxxxxxxxxxxxxxxx";
};
An alternative for code that does not use HttpWebRequest, and for environments where you can't install trusted certificates in the certificate store: Check the callback's error parameter, which will contain any error that were detected prior to the callback. This way, you can ignore errors for specific hash strings, but still accept other certificates that pass validation.
ServicePointManager.ServerCertificateValidationCallback +=
(sender, cert, chain, error) =>
{
if (cert.GetCertHashString() == "xxxxxxxxxxxxxxxx")
{
return true;
}
else
{
return error == SslPolicyErrors.None;
}
};
Reference:
https://msdn.microsoft.com/en-us/library/system.net.security.remotecertificatevalidationcallback(v=vs.110).aspx
Note that this will still affect other web client instances in the same appdomain (they will all accept the specified hash string), but at least it won't block other certificates.
The straight-forward approach for this scenario should be to install the two self-generated certificates in the trusted root stores on the client machines. You will get a security warning when you do this because the certificates can't be authenticated with Thawte or similar but after that regular secure communication should work. IIRC, you need to install the full (both public and private key) version in trusted root for this to work.
Bit late to the party, I know, but another option is to use a class inheriting IDisposable that can be put into a using(){} block around your code:
public class ServicePointManagerX509Helper : IDisposable
{
private readonly SecurityProtocolType _originalProtocol;
public ServicePointManagerX509Helper()
{
_originalProtocol = ServicePointManager.SecurityProtocol;
ServicePointManager.ServerCertificateValidationCallback += TrustingCallBack;
}
public void Dispose()
{
ServicePointManager.SecurityProtocol = _originalProtocol;
ServicePointManager.ServerCertificateValidationCallback -= TrustingCallBack;
}
private bool TrustingCallBack(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
// The logic for acceptance of your certificates here
return true;
}
}
Used in this fashion:
using (new ServicePointManagerX509Helper())
{
// Your code here
}

Categories