Specify the local endpoint for HTTP request - http

I am trying to specify the local endpoint for some HTTP requests that I need to send.
So, I have multiple IPs on my device (Wifi and Cellular) and would like to chose which one of those I want to use when sending HTTP request to get/post data from the server.
I learned that it is possible with HttpClient with .NET core 5.0 (that isn't supported with Xamarin)
HttpClient specify interface to bind to / make requests from
SocketsHttpHandler socketsHttpHandler = new SocketsHttpHandler();
socketsHttpHandler.ConnectCallback = async (context, token) =>
{
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
s.Bind(new IPEndPoint(IPAddress.Loopback, 0));
await s.ConnectAsync(context.DnsEndPoint, token);
s.NoDelay = true;
return new NetworkStream(s, ownsSocket: true);
};
using (HttpClient Client = new HttpClient(socketsHttpHandler))
{
Client.BaseAddress = new Uri("PutIPHere//");
HttpResponseMessage Response = await Client.GetAsync("");
// use Response as needed...
}
Another way is with reflection (but only with .NET framework), see L.B Answer
HttpClientHandler SetServicePointOptions(HttpClientHandler handler)
{
var field = handler.GetType().GetField("_startRequest", BindingFlags.NonPublic | BindingFlags.Instance); // Fieldname has a _ due to being private
var startRequest = (Action<object>)field.GetValue(handler);
Action<object> newStartRequest = obj =>
{
var webReqField = obj.GetType().GetField("webRequest", BindingFlags.NonPublic | BindingFlags.Instance);
var webRequest = webReqField.GetValue(obj) as HttpWebRequest;
webRequest.ServicePoint.BindIPEndPointDelegate = new BindIPEndPoint(BindIPEndPointCallback);
startRequest(obj); //call original action
};
field.SetValue(handler, newStartRequest); //replace original 'startRequest' with the one above
return handler;
}
private static IPEndPoint BindIPEndPointCallback(ServicePoint servicePoint, IPEndPoint remoteEndPoint, int retryCount)
{
return new IPEndPoint(IPAddress.Parse("PutIPHere"), 0);
}
Is there any way to specify the local endpoint for HTTP requests (using Xamarin)?
I would like to do it from the PLC, but also native to android and iOS should be ok.
Thanks in advance

Related

Server side validation for Cloudfare Turnstile reCaptcha

I am adding CF Turnstile recaptcha to my asp.net core web api for our contact us form and I am curious what IP address I should be using for this verification process. My code is as follows:
var dictionary = new Dictionary<string, string>
{
{ "secret", reCaptchaKey },
{ "response", customerInquiry.Token }
};
var postContent = new FormUrlEncodedContent(dictionary);
HttpResponseMessage recaptchaResponse = null;
string stringContent = "";
// Call recaptcha api and validate the token
using (var http = new HttpClient())
{
recaptchaResponse = await http.PostAsync("https://challenges.cloudflare.com/turnstile/v0/siteverify", postContent);
stringContent = await recaptchaResponse.Content.ReadAsStringAsync();
}
The example code on CF shows the following for their node.js ( I assume) implementation:
formData.append('secret', SECRET_KEY);
formData.append('response', token);
formData.append('remoteip', ip);

httpclient call is invoked after await keyword in dotnet core

I would like to do some operation which doesn't depend on API response and at the same time I want API to finish its process.
But in my case, API doesn't receive request when postasync is executed.
Instead, Web api receive request after await weatherForeCastdata.
I noticed strange behavior today
when I executed endpoint for first time(both the apis), webapi received request after postasync method. From second time, api receives request after executing await weatherForeCastdata.
I launched applictaion from vs code
browser : chrome
Can anyone help me ?
public async Task<IEnumerable<WeatherForecast>> Get()
{
var rng = new Random();
var weatherForeCastdata = new HttpClientCall<WeatherForecast>(_configuration).PostRequest(_configuration["Services:Payperiod"],new WeatherForecast());
Console.WriteLine("apiinvoked");
var data = await weatherForeCastdata;
//var data1 = await data.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<IEnumerable<WeatherForecast>>(data);
}
public class HttpClientCall<T> where T : class
{
HttpClientHandler httpClientHandler = new HttpClientHandler();
private readonly IConfiguration _configuration;
internal HttpClientCall(IConfiguration configuration)
{
httpClientHandler = new HttpClientHandler();
httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) =>
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true; //Is valid
}
return true;
};
_configuration = configuration;
}
public async Task<string> PostRequest(string apiUrl, T postObject)
{
using (var client = new HttpClient(httpClientHandler))
{
client.DefaultRequestHeaders.Add("ClientId", _configuration["header"]);
Console.WriteLine(apiUrl);
var response = client.PostAsync(apiUrl, postObject, new JsonMediaTypeFormatter());
var response1=await response;
return await response1.Content.ReadAsStringAsync();
}
}
}

Thirdparty certificate authentication in .net core API between client and server API

I am trying to implement the certificate authentication in .net core API(Server/target) and this API will be invoked in to another API(Client) .Here is the piece of code of client api which makes request to server/target api.But I'm facing an error on the server/target api .I'm running these two services from local and both certificates have already installed
Client side controller logic
[HttpGet]
public async Task<List<WeatherForecast>> Get()
{
List<WeatherForecast> weatherForecastList = new List<WeatherForecast>();
X509Certificate2 clientCert = Authentication.GetClientCertificate();
if (clientCert == null)
{
HttpActionContext actionContext = null;
actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Forbidden)
{
ReasonPhrase = "Client Certificate Required"
};
}
HttpClientHandler requestHandler = new HttpClientHandler();
requestHandler.ClientCertificates.Add(clientCert);
requestHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
HttpClient client = new HttpClient(requestHandler)
{
BaseAddress = new Uri("https://localhost:11111/ServerAPI")
};
client.DefaultRequestHeaders
.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/xml"));//ACCEPT head
using (var httpClient = new HttpClient())
{
//httpClient.DefaultRequestHeaders.Accept.Clear();
var request = new HttpRequestMessage()
{
RequestUri = new Uri("https://localhost:44386/ServerAPI"),
Method = HttpMethod.Get,
};
request.Headers.Add("X-ARR-ClientCert", clientCert.GetRawCertDataString());
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));//ACCEPT head
//using (var response = await httpClient.GetAsync("https://localhost:11111/ServerAPI"))
using (var response = await httpClient.SendAsync(request))
{
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
string apiResposne = await response.Content.ReadAsStringAsync();
weatherForecastList = JsonConvert.DeserializeObject<List<WeatherForecast>>(apiResposne);
}
}
}
return weatherForecastList;
}
authentication class
public static X509Certificate2 GetClientCertificate()
{
X509Store userCaStore = new X509Store(StoreName.TrustedPeople, StoreLocation.CurrentUser);
try
{
string str_API_Cert_Thumbprint = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
userCaStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certificatesInStore = userCaStore.Certificates;
X509Certificate2Collection findResult = certificatesInStore.Find(X509FindType.FindByThumbprint, str_API_Cert_Thumbprint, false);
X509Certificate2 clientCertificate = null;
if (findResult.Count == 1)
{
clientCertificate = findResult[0];
if(System.DateTime.Today >= System.Convert.ToDateTime(clientCertificate.GetExpirationDateString()))
{
throw new Exception("Certificate has already been expired.");
}
else if (System.Convert.ToDateTime(clientCertificate.GetExpirationDateString()).AddDays(-30) <= System.DateTime.Today)
{
throw new Exception("Certificate is about to expire in 30 days.");
}
}
else
{
throw new Exception("Unable to locate the correct client certificate.");
}
return clientCertificate;
}
catch (Exception ex)
{
throw;
}
finally
{
userCaStore.Close();
}
}
Server/target api code
[HttpGet]
public IEnumerable<WeatherForecast> Getcertdata()
{
IHeaderDictionary headers = base.Request.Headers;
X509Certificate2 clientCertificate = null;
string certHeaderString = headers["X-ARR-ClientCert"];
if (!string.IsNullOrEmpty(certHeaderString))
{
//byte[] bytes = Encoding.ASCII.GetBytes(certHeaderString);
//byte[] bytes = Convert.FromBase64String(certHeaderString);
//clientCertificate = new X509Certificate2(bytes);
clientCertificate = new X509Certificate2(WebUtility.UrlDecode(certHeaderString));
var serverCertificate = new X509Certificate2(Path.Combine("abc.pfx"), "pwd");
if (clientCertificate.Thumbprint == serverCertificate.Thumbprint)
{
//Valida Cert
}
}
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
}).ToArray();
//return new List<WeatherForecast>();
}
You have much more problems here, the code is significantly flawed and insecure in various ways. Let's explain each issue:
HttpClient in using clause in client side controller logic
Although you expect to wrap anything that implements IDisposable in using statement. However, it is not really the case with HttpClient. Connections are not closed immediately. And with every request to client controller action, a new connection is established to remote endpoint, while previous connections sit in TIME_WAIT state. Under certain constant load, your HttpClient will exhaust TCP port pool (which is limited) and any new attempt to create a new connection will throw an exception. Here are more details on this problem: You're using HttpClient wrong and it is destabilizing your software
Microsoft recommendation is to re-use existing connections. One way to do this is to Use IHttpClientFactory to implement resilient HTTP requests. Microsoft article talks a bit about this problem:
Though this class implements IDisposable, declaring and instantiating
it within a using statement is not preferred because when the
HttpClient object gets disposed of, the underlying socket is not
immediately released, which can lead to a socket exhaustion problem.
BTW, you have created a client variable, but do not use it in any way.
Ignore certificate validation problems
The line:
requestHandler.ServerCertificateCustomValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
make you vulnerable to MITM attack.
you are doing client certificate authentication wrong
The line:
request.Headers.Add("X-ARR-ClientCert", clientCert.GetRawCertDataString());
It is not the proper way how to do client cert authentication. What you literally doing is passing certificate's public part to server. That's all. You do not prove private key possession which is required to authenticate you. The proper way to do so is:
requestHandler.ClientCertificates.Add(clientCert);
This will force client and server to perform proper client authentication and check if you possess the private key for certificate you pass (it is done in TLS handshake automatically). If you have ASP.NET on server side, then you read it this way (in controller action):
X509Certificate2 clientCert = Request.HttpContext.Connection.ClientCertificate
if (clientCert == null) {
return Unauthorized();
}
// perform client cert validation according server-side rules.
Non-standard cert store
In authentication class you open StoreName.TrustedPeople store, while normally it should be StoreName.My. TrustedPeople isn't designed to store certs with private key. It isn't a functional problem, but it is bad practice.
unnecessary try/catch clause in authentication class
If you purposely throw exceptions in method, do not use try/catch. In your case you simply rethrow exception, thus you are doing a double work. And this:
throw new Exception("Certificate is about to expire in 30 days.");
is behind me. Throwing exception on technically valid certificate? Really?
server side code
As said, all this:
IHeaderDictionary headers = base.Request.Headers;
X509Certificate2 clientCertificate = null;
string certHeaderString = headers["X-ARR-ClientCert"];
if (!string.IsNullOrEmpty(certHeaderString))
{
//byte[] bytes = Encoding.ASCII.GetBytes(certHeaderString);
//byte[] bytes = Convert.FromBase64String(certHeaderString);
//clientCertificate = new X509Certificate2(bytes);
clientCertificate = new X509Certificate2(WebUtility.UrlDecode(certHeaderString));
var serverCertificate = new X509Certificate2(Path.Combine("abc.pfx"), "pwd");
if (clientCertificate.Thumbprint == serverCertificate.Thumbprint)
{
//Valida Cert
}
}
must be replaced with:
X509Certificate2 clientCert = Request.HttpContext.Connection.ClientCertificate
if (clientCert == null) {
return Unauthorized();
}
// perform client cert validation according server-side rules.
BTW:
var serverCertificate = new X509Certificate2(Path.Combine("abc.pfx"), "pwd");
if (clientCertificate.Thumbprint == serverCertificate.Thumbprint)
{
//Valida Cert
}
This is another disaster in your code. You are loading the server certificate from PFX just to compare their thumbprints? So, you suppose that client will have a copy of server certificate? Client and server certificates must not be the same. Next thing is you are generating a lot of copies of server certificate's private key files. More private key files you generate, the slower the process is and you just generate a lot of garbage. More details on this you can find in my blog post: Handling X509KeyStorageFlags in applications

Unable able to communicate between two services

I have created two Web API in Asp.net Core one is Wrapper Service to be deployed on DMZ Server and other is core service which have access to our DB Non DMZ. The problem i am facing is that i am unable to communicate between two services.
Both are running on local machine through dockers. When i try to hit core service running on url: https://localhost:56788/Rewards thorugh HttpClient/WebRequest i got message
No connection could be made because the target machine actively refused it.
DMZ Controller Logic
[Route("[controller]")]
[ApiController]
public class RewardsController : ControllerBase
{
[HttpGet]
public string Get()
{
string response = string.Empty;
//using (var client = new HttpClient())
//{
// client.BaseAddress = new Uri("http://localhost:5000/");
// //HTTP GET
// var responseTask = client.GetAsync("Rewards");
// responseTask.Wait();
// var result = responseTask.Result;
// if (result.IsSuccessStatusCode)
// {
// response = result.ToString();
// }
// else //web api sent error response
// {
// //log response status here..
// response = "Error";
// }
//}
string sURL = "https://localhost:56788/Rewards";
WebRequest wrPostURL = WebRequest.Create(sURL);
wrPostURL.Method = "GET";
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;
using (HttpWebResponse webresponse = wrPostURL.GetResponse() as HttpWebResponse)
{
Encoding enc = System.Text.Encoding.GetEncoding("utf-8");
StreamReader loResponseStream = new StreamReader(webresponse.GetResponseStream(), enc);
var jsonResponse = loResponseStream.ReadToEnd();
loResponseStream.Close();
webresponse.Close();
}
return response;
}
}
NON-DMZ Controller Logic
[Route("[controller]")]
[ApiController]
public class RewardsController : ControllerBase
{
[HttpGet]
public string Get()
{
return "Hello";
}
}
Update
This problem is due to dockers. When i deploy both APIs to IIS then it will work fine but i have to do it with Dockers
The problem occurs due to the fact that i was calling localhost to communicate with other API running on another container. When we call localhost the first container start looking for the service that is running on same container. By using VM/Local Machine IP instead of localhost the problem can be avoided.

Passing an image from a Wep Api service to another Web Api service

For security reasons, I am building two Web Api services. The first Web Api app will have access to an image generating service, and will act as a security proxy. The second Web Api app will call the first app from the internet and retrieve the image.
However, I can't seem to get to negotiate passage of the image correctly. My thought was to have the security proxy Web API to get the image, and then pass it as a byte array my other service which would allow a user to download the image. However, when my browser attempts to open the image, it is always corrupted.
Here is the security proxy getting the image, which I know is successful:
public byte[] Get(string invoice, string Customer)
{
object image;
try
{
image = _repo.GetImage(invoice, Customer);
}
catch (ApplicationException exc)
{
var resp = new HttpResponseMessage(HttpStatusCode.NotFound)
{
Content = new StringContent(string.Format("No Image with Invoice Number = {0}", invoice.ToString())),
ReasonPhrase = "Image Not Found"
};
throw new HttpResponseException(resp);
}
catch (Exception exc)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
return (byte[])image;
}
This returns an array with a length of 40133.
The calling Web API service looks like this:
public HttpResponseMessage Get(string invoice, string Customer)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/octet-stream"));
byte[] img = client.GetByteArrayAsync("http://localhost:1363/api/Image/" + invoice + "/" + Customer).Result;
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new ByteArrayContent(img);
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/tiff");
var disposition = new ContentDispositionHeaderValue("attachment");
disposition.FileName = "ImageDocument.tif";
response.Content.Headers.ContentDisposition = disposition;
return response;
}
However, the length of the img byte array is 53514.
When the browser tries to open the image, it tells me it is corrupt. If I open the TIFF in notepad, I get :
"SUkqAAgAAAASAP4ABAABAAAAAAAAAAABBAABAAAAsAYAAAEBBAABAAAAvgQAAAIBAwABAAAAAQAAAAMBAwABAAAABAAAAAYBAwABAAAAAAAAAAcBAwABAAAAAQAAABEBBAABAAAAAAMAABIBAwABAAAAAQAAABUBAwABAAAAAQAAABYBBAABAAAAvgQAABcBBAABAAAAxZkAABoBBQABAAAA+AIAABsBBQABAAAA8AIAACgBAwABAAAAAgAAADEBAgA4AAAAuAIAADIBAgAUAAAApAIAAOiAAwABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADIwMTI6MTA6MDMgMDc6Mjc6MTkAS29mYXggc3RhbmRhcmQgTXVsdGktUGFnZSBUSUZGIFN0b3JhZ2UgRmlsdGVyIHYzLjAzLjAwMADIAAAAAQAAAMgAAAABAAAALcMjAGC+cBQRwhOKcIuzqZDzrHoxF8k+VAOR2cAgjhC5lQEI+VYoiIiIiIiIiJXLAazgaZvMBqEcNI0BoJwaTMGsjgqGA1yOGaUA0Hg0igC5qZ6I1GSsNMuGqeBrI+bBoNYQrfNIiMREWdl4zVWRERkQzVECBpNcRyMCz6PhQgZwQGQLjpCIWwgxERGLLYAx//zLWLx4IeDnBnxSGFMRgIeZ4zcaR+KuPM4KeZ6MBTqKcj8YjAQ4IejDoQ4eE07WGnra3p9w07Xhw1s7NHu+0/v+/SQf6/9+cjwp0Z0Z8KeCm4p4IGQwhoz4cwCFBZN8u8s5duXeXTLva7pN6J56l45sf8u8u
SNIP*
Anyone know what I am doing wrong?
Thanks!
Chris
Solved
If anyone is interested in the calling code that leverages the solution identified, here it is:
public HttpResponseMessage Get(string invoice, string Customer)
{
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("image/tiff"));
byte[] img = client.GetByteArrayAsync("http://localhost:1363/api/Image/" + invoice + "/" + Customer).Result;
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new ByteArrayContent(img);
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/tiff");
var disposition = new ContentDispositionHeaderValue("attachment");
disposition.FileName = "ImageDocument.tif";
response.Content.Headers.ContentDisposition = disposition;
return response;
}
With your above current return type (byte[]) of action, formatters of web api are probably handling them and hence you are seeing unexpected response.
can you try sending the image as a ByteArrayContent instead?(you need to have HttpResponseMessage as a return type here)
Example:
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Content = new ByteArrayContent(..your byte array here...);
response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");
return response;

Resources